rubocop-rails 2.25.1 → 2.32.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 (80) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.txt +1 -1
  3. data/README.md +60 -8
  4. data/config/default.yml +103 -51
  5. data/lib/rubocop/cop/mixin/active_record_helper.rb +2 -2
  6. data/lib/rubocop/cop/mixin/active_record_migrations_helper.rb +2 -2
  7. data/lib/rubocop/cop/mixin/database_type_resolvable.rb +2 -2
  8. data/lib/rubocop/cop/mixin/enforce_superclass.rb +6 -1
  9. data/lib/rubocop/cop/mixin/index_method.rb +69 -61
  10. data/lib/rubocop/cop/mixin/routes_helper.rb +20 -0
  11. data/lib/rubocop/cop/mixin/target_rails_version.rb +3 -5
  12. data/lib/rubocop/cop/rails/action_order.rb +1 -5
  13. data/lib/rubocop/cop/rails/active_record_callbacks_order.rb +1 -5
  14. data/lib/rubocop/cop/rails/add_column_index.rb +1 -0
  15. data/lib/rubocop/cop/rails/application_record.rb +4 -0
  16. data/lib/rubocop/cop/rails/arel_star.rb +5 -5
  17. data/lib/rubocop/cop/rails/belongs_to.rb +1 -1
  18. data/lib/rubocop/cop/rails/blank.rb +1 -1
  19. data/lib/rubocop/cop/rails/bulk_change_table.rb +3 -2
  20. data/lib/rubocop/cop/rails/compact_blank.rb +29 -8
  21. data/lib/rubocop/cop/rails/content_tag.rb +1 -1
  22. data/lib/rubocop/cop/rails/dangerous_column_names.rb +2 -0
  23. data/lib/rubocop/cop/rails/date.rb +2 -2
  24. data/lib/rubocop/cop/rails/delegate.rb +53 -7
  25. data/lib/rubocop/cop/rails/duplicate_association.rb +8 -4
  26. data/lib/rubocop/cop/rails/eager_evaluation_log_message.rb +1 -3
  27. data/lib/rubocop/cop/rails/enum_hash.rb +31 -8
  28. data/lib/rubocop/cop/rails/enum_syntax.rb +130 -0
  29. data/lib/rubocop/cop/rails/enum_uniqueness.rb +29 -7
  30. data/lib/rubocop/cop/rails/env_local.rb +26 -3
  31. data/lib/rubocop/cop/rails/file_path.rb +62 -10
  32. data/lib/rubocop/cop/rails/http_positional_arguments.rb +7 -0
  33. data/lib/rubocop/cop/rails/ignored_skip_action_filter_option.rb +1 -1
  34. data/lib/rubocop/cop/rails/index_by.rb +37 -12
  35. data/lib/rubocop/cop/rails/index_with.rb +37 -12
  36. data/lib/rubocop/cop/rails/inquiry.rb +1 -1
  37. data/lib/rubocop/cop/rails/lexically_scoped_action_filter.rb +11 -1
  38. data/lib/rubocop/cop/rails/match_route.rb +1 -9
  39. data/lib/rubocop/cop/rails/multiple_route_paths.rb +50 -0
  40. data/lib/rubocop/cop/rails/not_null_column.rb +6 -2
  41. data/lib/rubocop/cop/rails/output.rb +1 -2
  42. data/lib/rubocop/cop/rails/pluck.rb +30 -4
  43. data/lib/rubocop/cop/rails/pluck_in_where.rb +17 -8
  44. data/lib/rubocop/cop/rails/pluralization_grammar.rb +30 -16
  45. data/lib/rubocop/cop/rails/presence.rb +1 -1
  46. data/lib/rubocop/cop/rails/present.rb +1 -3
  47. data/lib/rubocop/cop/rails/redundant_active_record_all_method.rb +1 -30
  48. data/lib/rubocop/cop/rails/redundant_foreign_key.rb +1 -1
  49. data/lib/rubocop/cop/rails/redundant_presence_validation_on_belongs_to.rb +9 -0
  50. data/lib/rubocop/cop/rails/redundant_receiver_in_with_options.rb +7 -2
  51. data/lib/rubocop/cop/rails/reflection_class_name.rb +3 -3
  52. data/lib/rubocop/cop/rails/relative_date_constant.rb +1 -1
  53. data/lib/rubocop/cop/rails/render_plain_text.rb +6 -3
  54. data/lib/rubocop/cop/rails/request_referer.rb +1 -1
  55. data/lib/rubocop/cop/rails/reversible_migration.rb +4 -1
  56. data/lib/rubocop/cop/rails/root_pathname_methods.rb +21 -12
  57. data/lib/rubocop/cop/rails/save_bang.rb +8 -7
  58. data/lib/rubocop/cop/rails/schema_comment.rb +2 -1
  59. data/lib/rubocop/cop/rails/select_map.rb +3 -2
  60. data/lib/rubocop/cop/rails/skips_model_validations.rb +5 -3
  61. data/lib/rubocop/cop/rails/squished_sql_heredocs.rb +1 -1
  62. data/lib/rubocop/cop/rails/strip_heredoc.rb +1 -1
  63. data/lib/rubocop/cop/rails/strong_parameters_expect.rb +104 -0
  64. data/lib/rubocop/cop/rails/three_state_boolean_column.rb +3 -2
  65. data/lib/rubocop/cop/rails/time_zone.rb +16 -7
  66. data/lib/rubocop/cop/rails/transaction_exit_statement.rb +7 -2
  67. data/lib/rubocop/cop/rails/uniq_before_pluck.rb +10 -33
  68. data/lib/rubocop/cop/rails/unique_validation_without_index.rb +1 -1
  69. data/lib/rubocop/cop/rails/validation.rb +1 -1
  70. data/lib/rubocop/cop/rails/where_equals.rb +28 -12
  71. data/lib/rubocop/cop/rails/where_not.rb +11 -6
  72. data/lib/rubocop/cop/rails/where_range.rb +7 -2
  73. data/lib/rubocop/cop/rails_cops.rb +4 -0
  74. data/lib/rubocop/rails/migration_file_skippable.rb +54 -0
  75. data/lib/rubocop/rails/plugin.rb +48 -0
  76. data/lib/rubocop/rails/version.rb +1 -1
  77. data/lib/rubocop/rails.rb +1 -8
  78. data/lib/rubocop-rails.rb +4 -5
  79. metadata +29 -12
  80. data/lib/rubocop/rails/inject.rb +0 -18
@@ -6,7 +6,72 @@ module RuboCop
6
6
  module IndexMethod # rubocop:disable Metrics/ModuleLength
7
7
  RESTRICT_ON_SEND = %i[each_with_object to_h map collect []].freeze
8
8
 
9
- def on_block(node) # rubocop:todo InternalAffairs/NumblockHandler
9
+ # Internal helper class to hold match data
10
+ Captures = ::Struct.new(
11
+ :transformed_argname,
12
+ :transforming_body_expr
13
+ ) do
14
+ def noop_transformation?
15
+ transforming_body_expr.lvar_type? && transforming_body_expr.children == [transformed_argname]
16
+ end
17
+ end
18
+
19
+ # Internal helper class to hold autocorrect data
20
+ Autocorrection = ::Struct.new(:match, :block_node, :leading, :trailing) do
21
+ def self.from_each_with_object(node, match)
22
+ new(match, node, 0, 0)
23
+ end
24
+
25
+ def self.from_to_h(node, match)
26
+ new(match, node, 0, 0)
27
+ end
28
+
29
+ def self.from_map_to_h(node, match)
30
+ if node.block_literal?
31
+ strip_trailing_chars = 0
32
+ else
33
+ map_range = node.children.first.source_range
34
+ node_range = node.source_range
35
+ strip_trailing_chars = node_range.end_pos - map_range.end_pos
36
+ end
37
+
38
+ new(match, node.children.first, 0, strip_trailing_chars)
39
+ end
40
+
41
+ def self.from_hash_brackets_map(node, match)
42
+ new(match, node.children.last, "#{node.receiver.source}[".length, ']'.length)
43
+ end
44
+
45
+ def strip_prefix_and_suffix(node, corrector)
46
+ expression = node.source_range
47
+ corrector.remove_leading(expression, leading)
48
+ corrector.remove_trailing(expression, trailing)
49
+ end
50
+
51
+ def set_new_method_name(new_method_name, corrector)
52
+ range = block_node.send_node.loc.selector
53
+ if (send_end = block_node.send_node.loc.end)
54
+ # If there are arguments (only true in the `each_with_object` case)
55
+ range = range.begin.join(send_end)
56
+ end
57
+ corrector.replace(range, new_method_name)
58
+ end
59
+
60
+ def set_new_arg_name(transformed_argname, corrector)
61
+ return unless block_node.block_type?
62
+
63
+ corrector.replace(block_node.arguments, "|#{transformed_argname}|")
64
+ end
65
+
66
+ def set_new_body_expression(transforming_body_expr, corrector)
67
+ body_source = transforming_body_expr.source
68
+ body_source = "{ #{body_source} }" if transforming_body_expr.hash_type? && !transforming_body_expr.braces?
69
+
70
+ corrector.replace(block_node.body, body_source)
71
+ end
72
+ end
73
+
74
+ def on_block(node)
10
75
  on_bad_each_with_object(node) do |*match|
11
76
  handle_possible_offense(node, match, 'each_with_object')
12
77
  end
@@ -18,6 +83,9 @@ module RuboCop
18
83
  end
19
84
  end
20
85
 
86
+ alias on_numblock on_block
87
+ alias on_itblock on_block
88
+
21
89
  def on_send(node)
22
90
  on_bad_map_to_h(node) do |*match|
23
91
  handle_possible_offense(node, match, 'map { ... }.to_h')
@@ -100,66 +168,6 @@ module RuboCop
100
168
  correction.set_new_arg_name(captures.transformed_argname, corrector)
101
169
  correction.set_new_body_expression(captures.transforming_body_expr, corrector)
102
170
  end
103
-
104
- # Internal helper class to hold match data
105
- Captures = ::Struct.new(
106
- :transformed_argname,
107
- :transforming_body_expr
108
- ) do
109
- def noop_transformation?
110
- transforming_body_expr.lvar_type? && transforming_body_expr.children == [transformed_argname]
111
- end
112
- end
113
-
114
- # Internal helper class to hold autocorrect data
115
- Autocorrection = ::Struct.new(:match, :block_node, :leading, :trailing) do
116
- def self.from_each_with_object(node, match)
117
- new(match, node, 0, 0)
118
- end
119
-
120
- def self.from_to_h(node, match)
121
- new(match, node, 0, 0)
122
- end
123
-
124
- def self.from_map_to_h(node, match)
125
- strip_trailing_chars = 0
126
-
127
- unless node.parent&.block_type?
128
- map_range = node.children.first.source_range
129
- node_range = node.source_range
130
- strip_trailing_chars = node_range.end_pos - map_range.end_pos
131
- end
132
-
133
- new(match, node.children.first, 0, strip_trailing_chars)
134
- end
135
-
136
- def self.from_hash_brackets_map(node, match)
137
- new(match, node.children.last, "#{node.receiver.source}[".length, ']'.length)
138
- end
139
-
140
- def strip_prefix_and_suffix(node, corrector)
141
- expression = node.source_range
142
- corrector.remove_leading(expression, leading)
143
- corrector.remove_trailing(expression, trailing)
144
- end
145
-
146
- def set_new_method_name(new_method_name, corrector)
147
- range = block_node.send_node.loc.selector
148
- if (send_end = block_node.send_node.loc.end)
149
- # If there are arguments (only true in the `each_with_object` case)
150
- range = range.begin.join(send_end)
151
- end
152
- corrector.replace(range, new_method_name)
153
- end
154
-
155
- def set_new_arg_name(transformed_argname, corrector)
156
- corrector.replace(block_node.arguments, "|#{transformed_argname}|")
157
- end
158
-
159
- def set_new_body_expression(transforming_body_expr, corrector)
160
- corrector.replace(block_node.body, transforming_body_expr.source)
161
- end
162
- end
163
171
  end
164
172
  end
165
173
  end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ # Common functionality for cops working with routes.
6
+ module RoutesHelper
7
+ extend NodePattern::Macros
8
+
9
+ HTTP_METHODS = %i[get post put patch delete].freeze
10
+
11
+ def_node_matcher :routes_draw?, <<~PATTERN
12
+ (send (send _ :routes) :draw)
13
+ PATTERN
14
+
15
+ def within_routes?(node)
16
+ node.each_ancestor(:block).any? { |block| routes_draw?(block.send_node) }
17
+ end
18
+ end
19
+ end
20
+ end
@@ -7,6 +7,9 @@ module RuboCop
7
7
  # Informs the base RuboCop gem that it the Rails version is checked via `requires_gem` API,
8
8
  # without needing to call this `#support_target_rails_version` method.
9
9
  USES_REQUIRES_GEM_API = true
10
+ # Look for `railties` instead of `rails`, to support apps that only use a subset of `rails`
11
+ # See https://github.com/rubocop/rubocop/pull/11289
12
+ TARGET_GEM_NAME = 'railties' # :nodoc:
10
13
 
11
14
  def minimum_target_rails_version(version)
12
15
  if respond_to?(:requires_gem)
@@ -33,11 +36,6 @@ module RuboCop
33
36
  @minimum_target_rails_version <= version
34
37
  end
35
38
  end
36
-
37
- # Look for `railties` instead of `rails`, to support apps that only use a subset of `rails`
38
- # See https://github.com/rubocop/rubocop/pull/11289
39
- TARGET_GEM_NAME = 'railties'
40
- private_constant :TARGET_GEM_NAME
41
39
  end
42
40
  end
43
41
  end
@@ -92,11 +92,7 @@ module RuboCop
92
92
  end
93
93
 
94
94
  def range_with_comments(node)
95
- # rubocop:todo InternalAffairs/LocationExpression
96
- # Using `RuboCop::Ext::Comment#source_range` requires RuboCop > 1.46,
97
- # which introduces https://github.com/rubocop/rubocop/pull/11630.
98
- ranges = [node, *processed_source.ast_with_comments[node]].map { |comment| comment.loc.expression }
99
- # rubocop:enable InternalAffairs/LocationExpression
95
+ ranges = [node, *processed_source.ast_with_comments[node]].map(&:source_range)
100
96
  ranges.reduce do |result, range|
101
97
  add_range(result, range)
102
98
  end
@@ -123,11 +123,7 @@ module RuboCop
123
123
  end
124
124
 
125
125
  def inline_comment?(comment)
126
- # rubocop:todo InternalAffairs/LocationExpression
127
- # Using `RuboCop::Ext::Comment#source_range` requires RuboCop > 1.46,
128
- # which introduces https://github.com/rubocop/rubocop/pull/11630.
129
- !comment_line?(comment.loc.expression.source_line)
130
- # rubocop:enable InternalAffairs/LocationExpression
126
+ !comment_line?(comment.source_range.source_line)
131
127
  end
132
128
 
133
129
  def start_line_position(node)
@@ -19,6 +19,7 @@ module RuboCop
19
19
  class AddColumnIndex < Base
20
20
  extend AutoCorrector
21
21
  include RangeHelp
22
+ include MigrationsHelper
22
23
 
23
24
  MSG = '`add_column` does not accept an `index` key, use `add_index` instead.'
24
25
  RESTRICT_ON_SEND = %i[add_column].freeze
@@ -5,6 +5,10 @@ module RuboCop
5
5
  module Rails
6
6
  # Checks that models subclass `ApplicationRecord` with Rails 5.0.
7
7
  #
8
+ # It is a common practice to define models inside migrations in order to retain forward
9
+ # compatibility by avoiding loading any application code. And so migration files are excluded
10
+ # by default for this cop.
11
+ #
8
12
  # @safety
9
13
  # This cop's autocorrection is unsafe because it may let the logic from `ApplicationRecord`
10
14
  # sneak into an Active Record model that is not purposed to inherit logic common among other
@@ -5,14 +5,14 @@ module RuboCop
5
5
  module Rails
6
6
  # Prevents usage of `"*"` on an Arel::Table column reference.
7
7
  #
8
- # Using `arel_table["*"]` causes the outputted string to be a literal
9
- # quoted asterisk (e.g. <tt>`my_model`.`*`</tt>). This causes the
10
- # database to look for a column named <tt>`*`</tt> (or `"*"`) as opposed
8
+ # Using `arel_table["\*"]` causes the outputted string to be a literal
9
+ # quoted asterisk (e.g. `my_model`.`*`). This causes the
10
+ # database to look for a column named `\*` (or `"*"`) as opposed
11
11
  # to expanding the column list as one would likely expect.
12
12
  #
13
13
  # @safety
14
- # This cop's autocorrection is unsafe because it turns a quoted `*` into
15
- # an SQL `*`, unquoted. `*` is a valid column name in certain databases
14
+ # This cop's autocorrection is unsafe because it turns a quoted `\*` into
15
+ # an SQL `*`, unquoted. `\*` is a valid column name in certain databases
16
16
  # supported by Rails, and even though it is usually a mistake,
17
17
  # it might denote legitimate access to a column named `*`.
18
18
  #
@@ -66,7 +66,7 @@ module RuboCop
66
66
 
67
67
  def_node_matcher :match_belongs_to_with_options, <<~PATTERN
68
68
  (send _ :belongs_to ...
69
- (hash <$(pair (sym :required) ${true false}) ...>)
69
+ (hash <$(pair (sym :required) $boolean) ...>)
70
70
  )
71
71
  PATTERN
72
72
 
@@ -123,7 +123,7 @@ module RuboCop
123
123
  def on_if(node)
124
124
  return unless cop_config['UnlessPresent']
125
125
  return unless node.unless?
126
- return if node.else? && config.for_cop('Style/UnlessElse')['Enabled']
126
+ return if node.else? && config.cop_enabled?('Style/UnlessElse')
127
127
 
128
128
  unless_present?(node) do |method_call, receiver|
129
129
  range = unless_condition(node, method_call)
@@ -65,6 +65,7 @@ module RuboCop
65
65
  # end
66
66
  class BulkChangeTable < Base
67
67
  include DatabaseTypeResolvable
68
+ include MigrationsHelper
68
69
 
69
70
  MSG_FOR_CHANGE_TABLE = <<~MSG.chomp
70
71
  You can combine alter queries using `bulk: true` options.
@@ -140,9 +141,9 @@ module RuboCop
140
141
  return unless support_bulk_alter?
141
142
  return unless node.command?(:change_table)
142
143
  return if include_bulk_options?(node)
143
- return unless node.block_node
144
+ return unless (body = node.block_node&.body)
144
145
 
145
- send_nodes = send_nodes_from_change_table_block(node.block_node.body)
146
+ send_nodes = send_nodes_from_change_table_block(body)
146
147
 
147
148
  add_offense_for_change_table(node) if count_transformations(send_nodes) > 1
148
149
  end
@@ -16,7 +16,6 @@ module RuboCop
16
16
  # And `compact_blank!` has different implementations for `Array`, `Hash`, and
17
17
  # `ActionController::Parameters`.
18
18
  # `Array#compact_blank!`, `Hash#compact_blank!` are equivalent to `delete_if(&:blank?)`.
19
- # `ActionController::Parameters#compact_blank!` is equivalent to `reject!(&:blank?)`.
20
19
  # If the cop makes a mistake, autocorrected code may get unexpected behavior.
21
20
  #
22
21
  # @example
@@ -24,6 +23,10 @@ module RuboCop
24
23
  # # bad
25
24
  # collection.reject(&:blank?)
26
25
  # collection.reject { |_k, v| v.blank? }
26
+ # collection.select(&:present?)
27
+ # collection.select { |_k, v| v.present? }
28
+ # collection.filter(&:present?)
29
+ # collection.filter { |_k, v| v.present? }
27
30
  #
28
31
  # # good
29
32
  # collection.compact_blank
@@ -31,8 +34,8 @@ module RuboCop
31
34
  # # bad
32
35
  # collection.delete_if(&:blank?) # Same behavior as `Array#compact_blank!` and `Hash#compact_blank!`
33
36
  # collection.delete_if { |_k, v| v.blank? } # Same behavior as `Array#compact_blank!` and `Hash#compact_blank!`
34
- # collection.reject!(&:blank?) # Same behavior as `ActionController::Parameters#compact_blank!`
35
- # collection.reject! { |_k, v| v.blank? } # Same behavior as `ActionController::Parameters#compact_blank!`
37
+ # collection.keep_if(&:present?) # Same behavior as `Array#compact_blank!` and `Hash#compact_blank!`
38
+ # collection.keep_if { |_k, v| v.present? } # Same behavior as `Array#compact_blank!` and `Hash#compact_blank!`
36
39
  #
37
40
  # # good
38
41
  # collection.compact_blank!
@@ -43,25 +46,41 @@ module RuboCop
43
46
  extend TargetRailsVersion
44
47
 
45
48
  MSG = 'Use `%<preferred_method>s` instead.'
46
- RESTRICT_ON_SEND = %i[reject delete_if reject!].freeze
49
+ RESTRICT_ON_SEND = %i[reject delete_if select filter keep_if].freeze
50
+ DESTRUCTIVE_METHODS = %i[delete_if keep_if].freeze
47
51
 
48
52
  minimum_target_rails_version 6.1
49
53
 
50
54
  def_node_matcher :reject_with_block?, <<~PATTERN
51
55
  (block
52
- (send _ {:reject :delete_if :reject!})
56
+ (send _ {:reject :delete_if})
53
57
  $(args ...)
54
58
  (send
55
59
  $(lvar _) :blank?))
56
60
  PATTERN
57
61
 
58
62
  def_node_matcher :reject_with_block_pass?, <<~PATTERN
59
- (send _ {:reject :delete_if :reject!}
63
+ (send _ {:reject :delete_if}
60
64
  (block_pass
61
65
  (sym :blank?)))
62
66
  PATTERN
63
67
 
68
+ def_node_matcher :select_with_block?, <<~PATTERN
69
+ (block
70
+ (send _ {:select :filter :keep_if})
71
+ $(args ...)
72
+ (send
73
+ $(lvar _) :present?))
74
+ PATTERN
75
+
76
+ def_node_matcher :select_with_block_pass?, <<~PATTERN
77
+ (send _ {:select :filter :keep_if}
78
+ (block-pass
79
+ (sym :present?)))
80
+ PATTERN
81
+
64
82
  def on_send(node)
83
+ return if target_ruby_version < 2.6 && node.method?(:filter)
65
84
  return unless bad_method?(node)
66
85
 
67
86
  range = offense_range(node)
@@ -75,8 +94,10 @@ module RuboCop
75
94
 
76
95
  def bad_method?(node)
77
96
  return true if reject_with_block_pass?(node)
97
+ return true if select_with_block_pass?(node)
78
98
 
79
- if (arguments, receiver_in_block = reject_with_block?(node.parent))
99
+ arguments, receiver_in_block = reject_with_block?(node.parent) || select_with_block?(node.parent)
100
+ if arguments
80
101
  return use_single_value_block_argument?(arguments, receiver_in_block) ||
81
102
  use_hash_value_block_argument?(arguments, receiver_in_block)
82
103
  end
@@ -103,7 +124,7 @@ module RuboCop
103
124
  end
104
125
 
105
126
  def preferred_method(node)
106
- node.method?(:reject) ? 'compact_blank' : 'compact_blank!'
127
+ DESTRUCTIVE_METHODS.include?(node.method_name) ? 'compact_blank!' : 'compact_blank'
107
128
  end
108
129
  end
109
130
  end
@@ -79,7 +79,7 @@ module RuboCop
79
79
  end
80
80
 
81
81
  def allowed_name?(argument)
82
- return false unless argument.str_type? || argument.sym_type?
82
+ return false unless argument.type?(:str, :sym)
83
83
 
84
84
  !/^[a-zA-Z-][a-zA-Z\-0-9]*$/.match?(argument.value)
85
85
  end
@@ -14,6 +14,8 @@ module RuboCop
14
14
  # # good
15
15
  # add_column :users, :saved
16
16
  class DangerousColumnNames < Base # rubocop:disable Metrics/ClassLength
17
+ include MigrationsHelper
18
+
17
19
  COLUMN_TYPE_METHOD_NAMES = %i[
18
20
  bigint
19
21
  binary
@@ -12,10 +12,10 @@ module RuboCop
12
12
  # The cop also reports warnings when you are using `to_time` method,
13
13
  # because it doesn't know about Rails time zone either.
14
14
  #
15
- # Two styles are supported for this cop. When `EnforcedStyle` is 'strict'
15
+ # Two styles are supported for this cop. When `EnforcedStyle` is `strict`
16
16
  # then the Date methods `today`, `current`, `yesterday`, and `tomorrow`
17
17
  # are prohibited and the usage of both `to_time`
18
- # and 'to_time_in_current_zone' are reported as warning.
18
+ # and `to_time_in_current_zone` are reported as warning.
19
19
  #
20
20
  # When `EnforcedStyle` is `flexible` then only `Date.today` is prohibited.
21
21
  #
@@ -15,6 +15,9 @@ module RuboCop
15
15
  # without using the `delegate` method will be a violation.
16
16
  # When set to `false`, this case is legal.
17
17
  #
18
+ # It is disabled for controllers in order to keep controller actions
19
+ # explicitly defined.
20
+ #
18
21
  # @example
19
22
  # # bad
20
23
  # def bar
@@ -68,12 +71,13 @@ module RuboCop
68
71
 
69
72
  def_node_matcher :delegate?, <<~PATTERN
70
73
  (def _method_name _args
71
- (send {(send nil? _) (self)} _ ...))
74
+ (send {(send nil? _) (self) (send (self) :class) ({cvar gvar ivar} _) (const _ _)} _ ...))
72
75
  PATTERN
73
76
 
74
77
  def on_def(node)
75
78
  return unless trivial_delegate?(node)
76
79
  return if private_or_protected_delegation(node)
80
+ return if module_function_declared?(node)
77
81
 
78
82
  register_offense(node)
79
83
  end
@@ -82,15 +86,40 @@ module RuboCop
82
86
 
83
87
  def register_offense(node)
84
88
  add_offense(node.loc.keyword) do |corrector|
85
- body = node.body
89
+ receiver = determine_register_offense_receiver(node.body.receiver)
90
+ delegation = build_delegation(node, receiver)
86
91
 
87
- receiver = body.receiver.self_type? ? 'self' : ":#{body.receiver.method_name}"
92
+ corrector.replace(node, delegation)
93
+ end
94
+ end
88
95
 
89
- delegation = ["delegate :#{body.method_name}", "to: #{receiver}"]
90
- delegation << ['prefix: true'] if node.method?(prefixed_method_name(node.body))
96
+ def determine_register_offense_receiver(receiver)
97
+ case receiver.type
98
+ when :self
99
+ 'self'
100
+ when :const
101
+ full_name = full_const_name(receiver)
102
+ full_name.include?('::') ? ":'#{full_name}'" : ":#{full_name}"
103
+ when :cvar, :gvar, :ivar
104
+ ":#{receiver.source}"
105
+ else
106
+ ":#{receiver.method_name}"
107
+ end
108
+ end
109
+
110
+ def build_delegation(node, receiver)
111
+ delegation = ["delegate :#{node.body.method_name}", "to: #{receiver}"]
112
+ delegation << ['prefix: true'] if node.method?(prefixed_method_name(node.body))
113
+ delegation.join(', ')
114
+ end
91
115
 
92
- corrector.replace(node, delegation.join(', '))
116
+ def full_const_name(node)
117
+ return unless node.const_type?
118
+ unless node.namespace
119
+ return node.absolute? ? "::#{node.source}" : node.source
93
120
  end
121
+
122
+ "#{full_const_name(node.namespace)}::#{node.short_name}"
94
123
  end
95
124
 
96
125
  def trivial_delegate?(def_node)
@@ -120,13 +149,30 @@ module RuboCop
120
149
  def prefixed_method_name(body)
121
150
  return '' if body.receiver.self_type?
122
151
 
123
- [body.receiver.method_name, body.method_name].join('_').to_sym
152
+ [determine_prefixed_method_receiver_name(body.receiver), body.method_name].join('_').to_sym
153
+ end
154
+
155
+ def determine_prefixed_method_receiver_name(receiver)
156
+ case receiver.type
157
+ when :cvar, :gvar, :ivar
158
+ receiver.source
159
+ when :const
160
+ full_const_name(receiver)
161
+ else
162
+ receiver.method_name.to_s
163
+ end
124
164
  end
125
165
 
126
166
  def private_or_protected_delegation(node)
127
167
  private_or_protected_inline(node) || node_visibility(node) != :public
128
168
  end
129
169
 
170
+ def module_function_declared?(node)
171
+ node.each_ancestor(:module, :begin).any? do |ancestor|
172
+ ancestor.children.any? { |child| child.send_type? && child.method?(:module_function) }
173
+ end
174
+ end
175
+
130
176
  def private_or_protected_inline(node)
131
177
  processed_source[node.first_line - 1].strip.match?(/\A(private )|(protected )/)
132
178
  end
@@ -63,11 +63,15 @@ module RuboCop
63
63
  private
64
64
 
65
65
  def register_offense(name, nodes, message_template)
66
- nodes.each do |node|
67
- add_offense(node, message: format(message_template, name: name)) do |corrector|
68
- next if same_line?(nodes.last, node)
66
+ last_node = nodes.last
69
67
 
70
- corrector.remove(range_by_whole_lines(node.source_range, include_final_newline: true))
68
+ nodes.each_with_index do |node, index|
69
+ add_offense(node, message: format(message_template, name: name)) do |corrector|
70
+ if index.zero?
71
+ corrector.replace(node, last_node.source)
72
+ else
73
+ corrector.remove(range_by_whole_lines(node.source_range, include_final_newline: true))
74
+ end
71
75
  end
72
76
  end
73
77
  end
@@ -45,12 +45,10 @@ module RuboCop
45
45
  return if node.parent&.block_type?
46
46
 
47
47
  interpolated_string_passed_to_debug(node) do |arguments|
48
- message = format(MSG)
49
-
50
48
  range = replacement_range(node)
51
49
  replacement = replacement_source(node, arguments)
52
50
 
53
- add_offense(range, message: message) do |corrector|
51
+ add_offense(range) do |corrector|
54
52
  corrector.replace(range, replacement)
55
53
  end
56
54
  end
@@ -12,6 +12,12 @@ module RuboCop
12
12
  #
13
13
  # @example
14
14
  # # bad
15
+ # enum :status, [:active, :archived]
16
+ #
17
+ # # good
18
+ # enum :status, { active: 0, archived: 1 }
19
+ #
20
+ # # bad
15
21
  # enum status: [:active, :archived]
16
22
  #
17
23
  # # good
@@ -23,7 +29,11 @@ module RuboCop
23
29
  MSG = 'Enum defined as an array found in `%<enum>s` enum declaration. Use hash syntax instead.'
24
30
  RESTRICT_ON_SEND = %i[enum].freeze
25
31
 
26
- def_node_matcher :enum?, <<~PATTERN
32
+ def_node_matcher :enum_with_array?, <<~PATTERN
33
+ (send nil? :enum $_ ${array} ...)
34
+ PATTERN
35
+
36
+ def_node_matcher :enum_with_old_syntax?, <<~PATTERN
27
37
  (send nil? :enum (hash $...))
28
38
  PATTERN
29
39
 
@@ -32,17 +42,19 @@ module RuboCop
32
42
  PATTERN
33
43
 
34
44
  def on_send(node)
35
- enum?(node) do |pairs|
45
+ target_rails_version >= 7.0 && enum_with_array?(node) do |key, array|
46
+ add_offense(array, message: message(key)) do |corrector|
47
+ corrector.replace(array, build_hash(array))
48
+ end
49
+ end
50
+
51
+ enum_with_old_syntax?(node) do |pairs|
36
52
  pairs.each do |pair|
37
53
  key, array = array_pair?(pair)
38
54
  next unless key
39
55
 
40
- add_offense(array, message: format(MSG, enum: enum_name(key))) do |corrector|
41
- hash = array.children.each_with_index.map do |elem, index|
42
- "#{source(elem)} => #{index}"
43
- end.join(', ')
44
-
45
- corrector.replace(array, "{#{hash}}")
56
+ add_offense(array, message: message(key)) do |corrector|
57
+ corrector.replace(array, build_hash(array))
46
58
  end
47
59
  end
48
60
  end
@@ -50,6 +62,10 @@ module RuboCop
50
62
 
51
63
  private
52
64
 
65
+ def message(key)
66
+ format(MSG, enum: enum_name(key))
67
+ end
68
+
53
69
  def enum_name(key)
54
70
  case key.type
55
71
  when :sym, :str
@@ -69,6 +85,13 @@ module RuboCop
69
85
  elem.source
70
86
  end
71
87
  end
88
+
89
+ def build_hash(array)
90
+ hash = array.children.each_with_index.map do |elem, index|
91
+ "#{source(elem)} => #{index}"
92
+ end.join(', ')
93
+ "{#{hash}}"
94
+ end
72
95
  end
73
96
  end
74
97
  end