rubocop-rails 2.29.0 → 2.31.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 (44) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +9 -7
  3. data/config/default.yml +18 -2
  4. data/lib/rubocop/cop/mixin/active_record_helper.rb +2 -2
  5. data/lib/rubocop/cop/mixin/active_record_migrations_helper.rb +2 -2
  6. data/lib/rubocop/cop/mixin/database_type_resolvable.rb +2 -2
  7. data/lib/rubocop/cop/mixin/enforce_superclass.rb +6 -1
  8. data/lib/rubocop/cop/mixin/index_method.rb +66 -65
  9. data/lib/rubocop/cop/rails/arel_star.rb +5 -5
  10. data/lib/rubocop/cop/rails/belongs_to.rb +1 -1
  11. data/lib/rubocop/cop/rails/blank.rb +1 -1
  12. data/lib/rubocop/cop/rails/content_tag.rb +1 -1
  13. data/lib/rubocop/cop/rails/delegate.rb +53 -7
  14. data/lib/rubocop/cop/rails/duplicate_association.rb +8 -4
  15. data/lib/rubocop/cop/rails/eager_evaluation_log_message.rb +1 -3
  16. data/lib/rubocop/cop/rails/file_path.rb +58 -9
  17. data/lib/rubocop/cop/rails/index_by.rb +9 -0
  18. data/lib/rubocop/cop/rails/index_with.rb +9 -0
  19. data/lib/rubocop/cop/rails/inquiry.rb +1 -1
  20. data/lib/rubocop/cop/rails/lexically_scoped_action_filter.rb +11 -1
  21. data/lib/rubocop/cop/rails/output.rb +1 -2
  22. data/lib/rubocop/cop/rails/pluck.rb +11 -5
  23. data/lib/rubocop/cop/rails/pluralization_grammar.rb +1 -1
  24. data/lib/rubocop/cop/rails/presence.rb +1 -1
  25. data/lib/rubocop/cop/rails/present.rb +1 -1
  26. data/lib/rubocop/cop/rails/redundant_receiver_in_with_options.rb +6 -1
  27. data/lib/rubocop/cop/rails/reflection_class_name.rb +2 -2
  28. data/lib/rubocop/cop/rails/relative_date_constant.rb +1 -1
  29. data/lib/rubocop/cop/rails/reversible_migration.rb +2 -1
  30. data/lib/rubocop/cop/rails/root_pathname_methods.rb +6 -1
  31. data/lib/rubocop/cop/rails/save_bang.rb +7 -6
  32. data/lib/rubocop/cop/rails/schema_comment.rb +1 -1
  33. data/lib/rubocop/cop/rails/skips_model_validations.rb +1 -1
  34. data/lib/rubocop/cop/rails/strip_heredoc.rb +1 -1
  35. data/lib/rubocop/cop/rails/strong_parameters_expect.rb +2 -2
  36. data/lib/rubocop/cop/rails/transaction_exit_statement.rb +2 -2
  37. data/lib/rubocop/cop/rails/uniq_before_pluck.rb +10 -33
  38. data/lib/rubocop/cop/rails/unique_validation_without_index.rb +1 -1
  39. data/lib/rubocop/rails/plugin.rb +48 -0
  40. data/lib/rubocop/rails/version.rb +1 -1
  41. data/lib/rubocop/rails.rb +1 -7
  42. data/lib/rubocop-rails.rb +1 -5
  43. metadata +23 -8
  44. data/lib/rubocop/rails/inject.rb +0 -18
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 210e46df0db68aeef5761f3afeeb86d62a52015be55190d3e6e1fd65368721be
4
- data.tar.gz: 7ff2ef268227cf39cb23b0479a8cf19c4ecc2acdf62c18f8e1766756e5965119
3
+ metadata.gz: ba4876948bedd432dc186a3c060c26c3808f1c7126b8fe69072e056a5f160d94
4
+ data.tar.gz: 7f890ec180a125abe4a40ad146661e71c088596eb093eacf12d322135c7f6907
5
5
  SHA512:
6
- metadata.gz: c84c5690318920b054525a7fc21ca4e0f067a2093757f7ff8182d429587c94898ba1ea1160893fe29e9a8ef9427f6cc076f613f283b5b857da0529fea7654a47
7
- data.tar.gz: ca501a5661bcebf2a6769a8cba620c637872e17023ac19fe8f6415b5ff4305d735ead0d24c7ffaee46f24c6cbeb466fcffeebdfb9cd96836211ab446643fc2a7
6
+ metadata.gz: 719fd043fd8ae7738eebb0c78abba01a23f2bc30c6d8c9825c89c7cbcc6cd48fd0077918e9bc84c9c471afecd62b69597d3f046c757606ecaec4af9daf8ef183
7
+ data.tar.gz: b7173eeaf159ee8673fe2f2c16a1c92bcbb55601d1202db18e5b48d09d1d1ef0207c17a182dcfb2b39c6e686d2b82871b191d5f90c4416a1af7d7b038a12c218
data/README.md CHANGED
@@ -5,7 +5,8 @@
5
5
 
6
6
  A [RuboCop](https://github.com/rubocop/rubocop) extension focused on enforcing Rails best practices and coding conventions.
7
7
 
8
- Note: This repository manages rubocop-rails gem (>= 2.0.0). rubocop-rails gem (<= 1.5.0) has been renamed to [rubocop-rails_config](https://rubygems.org/gems/rubocop-rails_config) gem.
8
+ > [!IMPORTANT]
9
+ > This repository manages rubocop-rails gem (>= 2.0.0). rubocop-rails gem (<= 1.5.0) has been renamed to [rubocop-rails_config](https://rubygems.org/gems/rubocop-rails_config) gem.
9
10
 
10
11
  ## Installation
11
12
 
@@ -31,13 +32,13 @@ ways to do this:
31
32
  Put this into your `.rubocop.yml`.
32
33
 
33
34
  ```yaml
34
- require: rubocop-rails
35
+ plugins: rubocop-rails
35
36
  ```
36
37
 
37
38
  Alternatively, use the following array notation when specifying multiple extensions.
38
39
 
39
40
  ```yaml
40
- require:
41
+ plugins:
41
42
  - rubocop-other-extension
42
43
  - rubocop-rails
43
44
  ```
@@ -45,21 +46,22 @@ require:
45
46
  Now you can run `rubocop` and it will automatically load the RuboCop Rails
46
47
  cops together with the standard cops.
47
48
 
49
+ > [!NOTE]
50
+ > The plugin system is supported in RuboCop 1.72+. In earlier versions, use `require` instead of `plugins`.
51
+
48
52
  ### Command line
49
53
 
50
54
  ```sh
51
- $ rubocop --require rubocop-rails
55
+ $ rubocop --plugin rubocop-rails
52
56
  ```
53
57
 
54
- Note: `--rails` option is required while `rubocop` command supports `--rails` option.
55
-
56
58
  ### Rake task
57
59
 
58
60
  ```ruby
59
61
  require 'rubocop/rake_task'
60
62
 
61
63
  RuboCop::RakeTask.new do |task|
62
- task.requires << 'rubocop-rails'
64
+ task.plugins << 'rubocop-rails'
63
65
  end
64
66
  ```
65
67
 
data/config/default.yml CHANGED
@@ -28,7 +28,7 @@ AllCops:
28
28
  # By specifying `MigratedSchemaVersion` option, migration files that have been migrated can be ignored.
29
29
  # When `MigratedSchemaVersion: '20241231000000'` is set. Migration files lower than or equal to '20250101000000' will be ignored.
30
30
  # For example, this is the timestamp in db/migrate/20250101000000_create_articles.rb.
31
- MigratedSchemaVersion: ~
31
+ MigratedSchemaVersion: '19700101000000' # NOTE: Used as a sentinel value for the UNIX epoch time.
32
32
 
33
33
  Lint/NumberConversion:
34
34
  # Add Rails' duration methods to the ignore list for `Lint/NumberConversion`
@@ -77,6 +77,20 @@ Lint/SafeNavigationChain:
77
77
  - try!
78
78
  - in?
79
79
 
80
+ Lint/UselessAccessModifier:
81
+ # Add methods from `ActiveSupport::Concern` and `Module::Concerning`:
82
+ # https://api.rubyonrails.org/classes/ActiveSupport/Concern.html
83
+ # https://api.rubyonrails.org/classes/Module/Concerning
84
+ inherit_mode:
85
+ merge:
86
+ - ContextCreatingMethods
87
+ ContextCreatingMethods:
88
+ - class_methods
89
+ - included
90
+ - prepended
91
+ - concern
92
+ - concerning
93
+
80
94
  Rails:
81
95
  Enabled: true
82
96
  DocumentationBaseURL: https://docs.rubocop.org/rubocop-rails
@@ -353,11 +367,13 @@ Rails/Delegate:
353
367
  Description: 'Prefer delegate method for delegations.'
354
368
  Enabled: true
355
369
  VersionAdded: '0.21'
356
- VersionChanged: '0.50'
370
+ VersionChanged: '2.30'
357
371
  # When set to true, using the target object as a prefix of the
358
372
  # method name without using the `delegate` method will be a
359
373
  # violation. When set to false, this case is legal.
360
374
  EnforceForPrefixed: true
375
+ Exclude:
376
+ - app/controllers/**/*.rb
361
377
 
362
378
  Rails/DelegateAllowBlank:
363
379
  Description: 'Do not use allow_blank as an option to delegate.'
@@ -87,7 +87,7 @@ module RuboCop
87
87
 
88
88
  options.each_pair.find do |pair|
89
89
  next unless pair.key.sym_type? && pair.key.value == :foreign_key
90
- next unless pair.value.sym_type? || pair.value.str_type?
90
+ next unless pair.value.type?(:sym, :str)
91
91
 
92
92
  break pair.value.value.to_s
93
93
  end
@@ -103,7 +103,7 @@ module RuboCop
103
103
  end
104
104
 
105
105
  def in_where?(node)
106
- send_node = node.each_ancestor(:send, :csend).first
106
+ send_node = node.each_ancestor(:call).first
107
107
  return false unless send_node
108
108
 
109
109
  return true if WHERE_METHODS.include?(send_node.method_name)
@@ -22,9 +22,9 @@ module RuboCop
22
22
  ].freeze
23
23
 
24
24
  def_node_matcher :create_table_with_block?, <<~PATTERN
25
- (block
25
+ (any_block
26
26
  (send nil? :create_table ...)
27
- (args (arg _var))
27
+ { _ | (args (arg _var)) }
28
28
  _)
29
29
  PATTERN
30
30
  end
@@ -29,8 +29,8 @@ module RuboCop
29
29
  end
30
30
 
31
31
  def database_from_env
32
- url = ENV['DATABASE_URL'].presence
33
- return unless url
32
+ url = ENV.fetch('DATABASE_URL', '')
33
+ return if url.blank?
34
34
 
35
35
  case url
36
36
  when %r{\A(mysql2|trilogy)://}
@@ -1,7 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RuboCop
4
- module Cop
4
+ module Cop # rubocop:disable Style/Documentation
5
+ # The EnforceSuperclass module is also defined in `rubocop` (for backwards
6
+ # compatibility), so here we remove it before (re)defining it, to avoid
7
+ # warnings about methods in the module being redefined.
8
+ remove_const(:EnforceSuperclass) if defined?(EnforceSuperclass)
9
+
5
10
  # Common functionality for enforcing a specific superclass.
6
11
  module EnforceSuperclass
7
12
  def self.included(base)
@@ -6,6 +6,71 @@ 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
+ # 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
+
9
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')
@@ -19,6 +84,7 @@ module RuboCop
19
84
  end
20
85
 
21
86
  alias on_numblock on_block
87
+ alias on_itblock on_block
22
88
 
23
89
  def on_send(node)
24
90
  on_bad_map_to_h(node) do |*match|
@@ -102,71 +168,6 @@ module RuboCop
102
168
  correction.set_new_arg_name(captures.transformed_argname, corrector)
103
169
  correction.set_new_body_expression(captures.transforming_body_expr, corrector)
104
170
  end
105
-
106
- # Internal helper class to hold match data
107
- Captures = ::Struct.new(
108
- :transformed_argname,
109
- :transforming_body_expr
110
- ) do
111
- def noop_transformation?
112
- transforming_body_expr.lvar_type? && transforming_body_expr.children == [transformed_argname]
113
- end
114
- end
115
-
116
- # Internal helper class to hold autocorrect data
117
- Autocorrection = ::Struct.new(:match, :block_node, :leading, :trailing) do
118
- def self.from_each_with_object(node, match)
119
- new(match, node, 0, 0)
120
- end
121
-
122
- def self.from_to_h(node, match)
123
- new(match, node, 0, 0)
124
- end
125
-
126
- def self.from_map_to_h(node, match)
127
- if node.block_literal?
128
- strip_trailing_chars = 0
129
- else
130
- map_range = node.children.first.source_range
131
- node_range = node.source_range
132
- strip_trailing_chars = node_range.end_pos - map_range.end_pos
133
- end
134
-
135
- new(match, node.children.first, 0, strip_trailing_chars)
136
- end
137
-
138
- def self.from_hash_brackets_map(node, match)
139
- new(match, node.children.last, "#{node.receiver.source}[".length, ']'.length)
140
- end
141
-
142
- def strip_prefix_and_suffix(node, corrector)
143
- expression = node.source_range
144
- corrector.remove_leading(expression, leading)
145
- corrector.remove_trailing(expression, trailing)
146
- end
147
-
148
- def set_new_method_name(new_method_name, corrector)
149
- range = block_node.send_node.loc.selector
150
- if (send_end = block_node.send_node.loc.end)
151
- # If there are arguments (only true in the `each_with_object` case)
152
- range = range.begin.join(send_end)
153
- end
154
- corrector.replace(range, new_method_name)
155
- end
156
-
157
- def set_new_arg_name(transformed_argname, corrector)
158
- return if block_node.numblock_type?
159
-
160
- corrector.replace(block_node.arguments, "|#{transformed_argname}|")
161
- end
162
-
163
- def set_new_body_expression(transforming_body_expr, corrector)
164
- body_source = transforming_body_expr.source
165
- body_source = "{ #{body_source} }" if transforming_body_expr.hash_type? && !transforming_body_expr.braces?
166
-
167
- corrector.replace(block_node.body, body_source)
168
- end
169
- end
170
171
  end
171
172
  end
172
173
  end
@@ -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)
@@ -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
@@ -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
@@ -6,6 +6,9 @@ module RuboCop
6
6
  # Identifies usages of file path joining process to use `Rails.root.join` clause.
7
7
  # It is used to add uniformity when joining paths.
8
8
  #
9
+ # NOTE: This cop ignores leading slashes in string literal arguments for `Rails.root.join`
10
+ # and multiple slashes in string literal arguments for `Rails.root.join` and `File.join`.
11
+ #
9
12
  # @example EnforcedStyle: slashes (default)
10
13
  # # bad
11
14
  # Rails.root.join('app', 'models', 'goober')
@@ -97,7 +100,7 @@ module RuboCop
97
100
 
98
101
  def check_for_file_join_with_rails_root(node)
99
102
  return unless file_join_nodes?(node)
100
- return unless node.arguments.any? { |e| rails_root_nodes?(e) }
103
+ return unless valid_arguments_for_file_join_with_rails_root?(node.arguments)
101
104
 
102
105
  register_offense(node, require_to_s: true) do |corrector|
103
106
  autocorrect_file_join(corrector, node) unless node.first_argument.array_type?
@@ -108,8 +111,7 @@ module RuboCop
108
111
  return unless style == :slashes
109
112
  return unless rails_root_nodes?(node)
110
113
  return unless rails_root_join_nodes?(node)
111
- return unless node.arguments.size > 1
112
- return unless node.arguments.all?(&:str_type?)
114
+ return unless valid_string_arguments_for_rails_root_join?(node.arguments)
113
115
 
114
116
  register_offense(node, require_to_s: false) do |corrector|
115
117
  autocorrect_rails_root_join_with_string_arguments(corrector, node)
@@ -120,15 +122,42 @@ module RuboCop
120
122
  return unless style == :arguments
121
123
  return unless rails_root_nodes?(node)
122
124
  return unless rails_root_join_nodes?(node)
123
- return unless node.arguments.any? { |arg| string_with_slash?(arg) }
125
+ return unless valid_slash_separated_path_for_rails_root_join?(node.arguments)
124
126
 
125
127
  register_offense(node, require_to_s: false) do |corrector|
126
128
  autocorrect_rails_root_join_with_slash_separated_path(corrector, node)
127
129
  end
128
130
  end
129
131
 
130
- def string_with_slash?(node)
131
- node.str_type? && node.source.include?(File::SEPARATOR)
132
+ def valid_arguments_for_file_join_with_rails_root?(arguments)
133
+ return false unless arguments.any? { |arg| rails_root_nodes?(arg) }
134
+
135
+ arguments.none? { |arg| arg.variable? || arg.const_type? || string_contains_multiple_slashes?(arg) }
136
+ end
137
+
138
+ def valid_string_arguments_for_rails_root_join?(arguments)
139
+ return false unless arguments.size > 1
140
+ return false unless arguments.all?(&:str_type?)
141
+
142
+ arguments.none? { |arg| string_with_leading_slash?(arg) || string_contains_multiple_slashes?(arg) }
143
+ end
144
+
145
+ def valid_slash_separated_path_for_rails_root_join?(arguments)
146
+ return false unless arguments.any? { |arg| string_contains_slash?(arg) }
147
+
148
+ arguments.none? { |arg| string_with_leading_slash?(arg) || string_contains_multiple_slashes?(arg) }
149
+ end
150
+
151
+ def string_contains_slash?(node)
152
+ node.str_type? && node.value.include?(File::SEPARATOR)
153
+ end
154
+
155
+ def string_contains_multiple_slashes?(node)
156
+ node.str_type? && node.value.include?('//')
157
+ end
158
+
159
+ def string_with_leading_slash?(node)
160
+ node.str_type? && node.value.start_with?(File::SEPARATOR)
132
161
  end
133
162
 
134
163
  def register_offense(node, require_to_s:, &block)
@@ -173,7 +202,17 @@ module RuboCop
173
202
  end
174
203
 
175
204
  def autocorrect_file_join(corrector, node)
205
+ replace_receiver_with_rails_root(corrector, node)
206
+ remove_first_argument_with_comma(corrector, node)
207
+ process_arguments(corrector, node.arguments)
208
+ append_to_string_conversion(corrector, node)
209
+ end
210
+
211
+ def replace_receiver_with_rails_root(corrector, node)
176
212
  corrector.replace(node.receiver, 'Rails.root')
213
+ end
214
+
215
+ def remove_first_argument_with_comma(corrector, node)
177
216
  corrector.remove(
178
217
  range_with_surrounding_space(
179
218
  range_with_surrounding_comma(
@@ -183,9 +222,19 @@ module RuboCop
183
222
  side: :right
184
223
  )
185
224
  )
186
- node.arguments.filter(&:str_type?).each do |argument|
187
- corrector.replace(argument, argument.value.delete_prefix('/').inspect)
225
+ end
226
+
227
+ def process_arguments(corrector, arguments)
228
+ arguments.each do |argument|
229
+ if argument.str_type?
230
+ corrector.replace(argument, argument.value.delete_prefix('/').inspect)
231
+ elsif argument.array_type?
232
+ corrector.replace(argument, "*#{argument.source}")
233
+ end
188
234
  end
235
+ end
236
+
237
+ def append_to_string_conversion(corrector, node)
189
238
  corrector.insert_after(node, '.to_s')
190
239
  end
191
240
 
@@ -206,7 +255,7 @@ module RuboCop
206
255
 
207
256
  def autocorrect_rails_root_join_with_slash_separated_path(corrector, node)
208
257
  node.arguments.each do |argument|
209
- next unless string_with_slash?(argument)
258
+ next unless string_contains_slash?(argument)
210
259
 
211
260
  index = argument.source.index(File::SEPARATOR)
212
261
  rest = inner_range_of(argument).adjust(begin_pos: index - 1)
@@ -37,6 +37,9 @@ module RuboCop
37
37
  (numblock
38
38
  (call _ :to_h) $1
39
39
  (array $_ (lvar :_1)))
40
+ (itblock
41
+ (call _ :to_h) $:it
42
+ (array $_ (lvar :it)))
40
43
  }
41
44
  PATTERN
42
45
 
@@ -50,6 +53,9 @@ module RuboCop
50
53
  (numblock
51
54
  (call _ {:map :collect}) $1
52
55
  (array $_ (lvar :_1)))
56
+ (itblock
57
+ (call _ {:map :collect}) $:it
58
+ (array $_ (lvar :it)))
53
59
  }
54
60
  :to_h)
55
61
  PATTERN
@@ -66,6 +72,9 @@ module RuboCop
66
72
  (numblock
67
73
  (call _ {:map :collect}) $1
68
74
  (array $_ (lvar :_1)))
75
+ (itblock
76
+ (call _ {:map :collect}) $:it
77
+ (array $_ (lvar :it)))
69
78
  }
70
79
  )
71
80
  PATTERN