rubocop-rails 2.17.2 → 2.17.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/config/default.yml +1 -1
  3. data/lib/rubocop/cop/mixin/active_record_helper.rb +2 -2
  4. data/lib/rubocop/cop/mixin/index_method.rb +1 -1
  5. data/lib/rubocop/cop/mixin/migrations_helper.rb +1 -1
  6. data/lib/rubocop/cop/rails/action_controller_flash_before_render.rb +9 -6
  7. data/lib/rubocop/cop/rails/action_controller_test_case.rb +1 -1
  8. data/lib/rubocop/cop/rails/action_order.rb +39 -3
  9. data/lib/rubocop/cop/rails/application_controller.rb +1 -1
  10. data/lib/rubocop/cop/rails/application_job.rb +1 -1
  11. data/lib/rubocop/cop/rails/application_mailer.rb +1 -1
  12. data/lib/rubocop/cop/rails/application_record.rb +1 -1
  13. data/lib/rubocop/cop/rails/content_tag.rb +1 -1
  14. data/lib/rubocop/cop/rails/dot_separated_keys.rb +1 -1
  15. data/lib/rubocop/cop/rails/duration_arithmetic.rb +2 -2
  16. data/lib/rubocop/cop/rails/dynamic_find_by.rb +16 -6
  17. data/lib/rubocop/cop/rails/file_path.rb +3 -3
  18. data/lib/rubocop/cop/rails/freeze_time.rb +7 -5
  19. data/lib/rubocop/cop/rails/has_many_or_has_one_dependent.rb +1 -1
  20. data/lib/rubocop/cop/rails/helper_instance_variable.rb +1 -1
  21. data/lib/rubocop/cop/rails/i18n_lazy_lookup.rb +2 -0
  22. data/lib/rubocop/cop/rails/i18n_locale_texts.rb +2 -2
  23. data/lib/rubocop/cop/rails/index_by.rb +1 -1
  24. data/lib/rubocop/cop/rails/index_with.rb +1 -1
  25. data/lib/rubocop/cop/rails/mailer_name.rb +3 -3
  26. data/lib/rubocop/cop/rails/migration_class_name.rb +1 -1
  27. data/lib/rubocop/cop/rails/output.rb +1 -1
  28. data/lib/rubocop/cop/rails/require_dependency.rb +1 -1
  29. data/lib/rubocop/cop/rails/root_pathname_methods.rb +38 -14
  30. data/lib/rubocop/cop/rails/short_i18n.rb +1 -1
  31. data/lib/rubocop/cop/rails/skips_model_validations.rb +1 -1
  32. data/lib/rubocop/cop/rails/time_zone.rb +15 -2
  33. data/lib/rubocop/cop/rails/time_zone_assignment.rb +1 -1
  34. data/lib/rubocop/cop/rails/to_s_with_argument.rb +38 -1
  35. data/lib/rubocop/cop/rails/unique_validation_without_index.rb +10 -1
  36. data/lib/rubocop/cop/rails/where_missing.rb +9 -2
  37. data/lib/rubocop/rails/version.rb +1 -1
  38. metadata +3 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 97069bbada22491ece697f32f933ce8dc5274c18ad2782bb832cd83b168874a3
4
- data.tar.gz: 4a691068f93c7be89e9cf695c3be16fe183cb23be4b187e822a9bf9d896c9af7
3
+ metadata.gz: 8a9beeaefa16cbe4afb5d598878444a835536ccda5d9b46c3e44e111513f1d8e
4
+ data.tar.gz: 669a9681ce5ab1fca038229a831f7e5acae6bbdcc3a58ecfc88d65fd54ef7190
5
5
  SHA512:
6
- metadata.gz: 213e3da3d5e23338c53ee9428d8d44a57123fa8a239c3974e5eb0181797555639da23ed7e09336e906e14ba3c99c46e33efba18db44646f0efe86913dc0b7847
7
- data.tar.gz: ed27cd2b9106a650b01fc700e9c2511971853412f49aa93876589dfa67e77c7a246fa364dd4682747fc97ffe8121d286a4096e6ef0b257483668c36f99fec9e1
6
+ metadata.gz: 72ca38ec9a9136bfa5bd635db9290314a4d1a4c8e3942f15aa62a823885ff1a1783f884598dce6f52765db75daccf8e4054d3faecdf973da6f2c53b10dc2c153
7
+ data.tar.gz: 536c7d8f5dfec22444d2d0fc4b297eac18f7b87cc6c871ce250bec58a2a6d380e814749534e5aedc47c0a66c7ae80ac8c61ffec330093c575cd1cc8dd76e4de9
data/config/default.yml CHANGED
@@ -540,7 +540,7 @@ Rails/I18nLazyLookup:
540
540
  Enabled: pending
541
541
  VersionAdded: '2.14'
542
542
  Include:
543
- - 'controllers/**/*'
543
+ - 'app/controllers/**/*.rb'
544
544
 
545
545
  Rails/I18nLocaleAssignment:
546
546
  Description: 'Prefer the usage of `I18n.with_locale` instead of manually updating `I18n.locale` value.'
@@ -10,8 +10,8 @@ module RuboCop
10
10
 
11
11
  def_node_matcher :active_record?, <<~PATTERN
12
12
  {
13
- (const nil? :ApplicationRecord)
14
- (const (const nil? :ActiveRecord) :Base)
13
+ (const {nil? cbase} :ApplicationRecord)
14
+ (const (const {nil? cbase} :ActiveRecord) :Base)
15
15
  }
16
16
  PATTERN
17
17
 
@@ -134,7 +134,7 @@ module RuboCop
134
134
  end
135
135
 
136
136
  def self.from_hash_brackets_map(node, match)
137
- new(match, node.children.last, 'Hash['.length, ']'.length)
137
+ new(match, node.children.last, "#{node.receiver.source}[".length, ']'.length)
138
138
  end
139
139
 
140
140
  def strip_prefix_and_suffix(node, corrector)
@@ -8,7 +8,7 @@ module RuboCop
8
8
 
9
9
  def_node_matcher :migration_class?, <<~PATTERN
10
10
  (class
11
- (const nil? _)
11
+ (const {nil? cbase} _)
12
12
  (send
13
13
  (const (const {nil? cbase} :ActiveRecord) :Migration)
14
14
  :[]
@@ -43,8 +43,8 @@ module RuboCop
43
43
 
44
44
  def_node_search :action_controller?, <<~PATTERN
45
45
  {
46
- (const nil? :ApplicationController)
47
- (const (const nil? :ActionController) :Base)
46
+ (const {nil? cbase} :ApplicationController)
47
+ (const (const {nil? cbase} :ActionController) :Base)
48
48
  }
49
49
  PATTERN
50
50
 
@@ -69,12 +69,15 @@ module RuboCop
69
69
  def followed_by_render?(flash_node)
70
70
  flash_assigment_node = find_ancestor(flash_node, type: :send)
71
71
  context = flash_assigment_node
72
- context = context.parent if context.parent.if_type?
72
+ if (node = context.each_ancestor(:if, :rescue).first)
73
+ context = node
74
+ elsif context.right_siblings.empty?
75
+ return true
76
+ end
73
77
  context = context.right_siblings
74
- return true if context.empty?
75
78
 
76
- context.compact.any? do |node|
77
- render?(node)
79
+ context.compact.any? do |render_candidate|
80
+ render?(render_candidate)
78
81
  end
79
82
  end
80
83
 
@@ -30,7 +30,7 @@ module RuboCop
30
30
 
31
31
  def_node_matcher :action_controller_test_case?, <<~PATTERN
32
32
  (class
33
- (const nil? _)
33
+ (const {nil? cbase} _)
34
34
  (const (const {nil? cbase} :ActionController) :TestCase) _)
35
35
  PATTERN
36
36
 
@@ -6,7 +6,8 @@ module RuboCop
6
6
  # Enforces consistent ordering of the standard Rails RESTful controller actions.
7
7
  #
8
8
  # The cop is configurable and can enforce any ordering of the standard actions.
9
- # All other methods are ignored.
9
+ # All other methods are ignored. So, the actions specified in `ExpectedOrder` should be
10
+ # defined before actions not specified.
10
11
  #
11
12
  # [source,yaml]
12
13
  # ----
@@ -35,6 +36,7 @@ module RuboCop
35
36
  extend AutoCorrector
36
37
  include VisibilityHelp
37
38
  include DefNode
39
+ include RangeHelp
38
40
 
39
41
  MSG = 'Action `%<current>s` should appear before `%<previous>s`.'
40
42
 
@@ -71,9 +73,43 @@ module RuboCop
71
73
  current: current.method_name
72
74
  )
73
75
  add_offense(current, message: message) do |corrector|
74
- corrector.replace(current, previous.source)
75
- corrector.replace(previous, current.source)
76
+ current = correction_target(current)
77
+ previous = correction_target(previous)
78
+
79
+ swap_range(corrector, current, previous)
80
+ end
81
+ end
82
+
83
+ def correction_target(def_node)
84
+ range_with_comments_and_lines(def_node.each_ancestor(:if).first || def_node)
85
+ end
86
+
87
+ def add_range(range1, range2)
88
+ range1.with(
89
+ begin_pos: [range1.begin_pos, range2.begin_pos].min,
90
+ end_pos: [range1.end_pos, range2.end_pos].max
91
+ )
92
+ end
93
+
94
+ def range_with_comments(node)
95
+ ranges = [
96
+ node,
97
+ *processed_source.ast_with_comments[node]
98
+ ].map do |element|
99
+ element.location.expression
76
100
  end
101
+ ranges.reduce do |result, range|
102
+ add_range(result, range)
103
+ end
104
+ end
105
+
106
+ def range_with_comments_and_lines(node)
107
+ range_by_whole_lines(range_with_comments(node), include_final_newline: true)
108
+ end
109
+
110
+ def swap_range(corrector, range1, range2)
111
+ corrector.insert_before(range2, range1.source)
112
+ corrector.remove(range1)
77
113
  end
78
114
  end
79
115
  end
@@ -25,7 +25,7 @@ module RuboCop
25
25
 
26
26
  MSG = 'Controllers should subclass `ApplicationController`.'
27
27
  SUPERCLASS = 'ApplicationController'
28
- BASE_PATTERN = '(const (const nil? :ActionController) :Base)'
28
+ BASE_PATTERN = '(const (const {nil? cbase} :ActionController) :Base)'
29
29
 
30
30
  # rubocop:disable Layout/ClassStructure
31
31
  include RuboCop::Cop::EnforceSuperclass
@@ -28,7 +28,7 @@ module RuboCop
28
28
 
29
29
  MSG = 'Jobs should subclass `ApplicationJob`.'
30
30
  SUPERCLASS = 'ApplicationJob'
31
- BASE_PATTERN = '(const (const nil? :ActiveJob) :Base)'
31
+ BASE_PATTERN = '(const (const {nil? cbase} :ActiveJob) :Base)'
32
32
 
33
33
  # rubocop:disable Layout/ClassStructure
34
34
  include RuboCop::Cop::EnforceSuperclass
@@ -28,7 +28,7 @@ module RuboCop
28
28
 
29
29
  MSG = 'Mailers should subclass `ApplicationMailer`.'
30
30
  SUPERCLASS = 'ApplicationMailer'
31
- BASE_PATTERN = '(const (const nil? :ActionMailer) :Base)'
31
+ BASE_PATTERN = '(const (const {nil? cbase} :ActionMailer) :Base)'
32
32
 
33
33
  # rubocop:disable Layout/ClassStructure
34
34
  include RuboCop::Cop::EnforceSuperclass
@@ -29,7 +29,7 @@ module RuboCop
29
29
 
30
30
  MSG = 'Models should subclass `ApplicationRecord`.'
31
31
  SUPERCLASS = 'ApplicationRecord'
32
- BASE_PATTERN = '(const (const nil? :ActiveRecord) :Base)'
32
+ BASE_PATTERN = '(const (const {nil? cbase} :ActiveRecord) :Base)'
33
33
 
34
34
  # rubocop:disable Layout/ClassStructure
35
35
  include RuboCop::Cop::EnforceSuperclass
@@ -81,7 +81,7 @@ module RuboCop
81
81
  def allowed_name?(argument)
82
82
  return false unless argument.str_type? || argument.sym_type?
83
83
 
84
- !/^[a-zA-Z\-][a-zA-Z\-0-9]*$/.match?(argument.value)
84
+ !/^[a-zA-Z-][a-zA-Z\-0-9]*$/.match?(argument.value)
85
85
  end
86
86
 
87
87
  def correction_range(node)
@@ -24,7 +24,7 @@ module RuboCop
24
24
  TRANSLATE_METHODS = %i[translate t].freeze
25
25
 
26
26
  def_node_matcher :translate_with_scope?, <<~PATTERN
27
- (send {nil? (const nil? :I18n)} {:translate :t} ${sym_type? str_type?}
27
+ (send {nil? (const {nil? cbase} :I18n)} {:translate :t} ${sym_type? str_type?}
28
28
  (hash <$(pair (sym :scope) ${array_type? sym_type?}) ...>)
29
29
  )
30
30
  PATTERN
@@ -70,8 +70,8 @@ module RuboCop
70
70
  # @return [Boolean] true if matches
71
71
  def_node_matcher :time_current?, <<~PATTERN
72
72
  {
73
- (send (const _ :Time) :current)
74
- (send (send (const _ :Time) :zone) :now)
73
+ (send (const {nil? cbase} :Time) :current)
74
+ (send (send (const {nil? cbase} :Time) :zone) :now)
75
75
  }
76
76
  PATTERN
77
77
 
@@ -53,7 +53,7 @@ module RuboCop
53
53
  method_name = node.method_name
54
54
  static_name = static_method_name(method_name)
55
55
  return unless static_name
56
- return if node.arguments.any? { |argument| IGNORED_ARGUMENT_TYPES.include?(argument.type) }
56
+ return unless dynamic_find_by_arguments?(node)
57
57
 
58
58
  message = format(MSG, static_name: static_name, method: method_name)
59
59
  add_offense(node, message: message) do |corrector|
@@ -65,12 +65,8 @@ module RuboCop
65
65
  private
66
66
 
67
67
  def autocorrect(corrector, node)
68
- keywords = column_keywords(node.method_name)
69
-
70
- return if keywords.size != node.arguments.size
71
-
72
68
  autocorrect_method_name(corrector, node)
73
- autocorrect_argument_keywords(corrector, node, keywords)
69
+ autocorrect_argument_keywords(corrector, node, column_keywords(node.method_name))
74
70
  end
75
71
 
76
72
  def allowed_invocation?(node)
@@ -120,6 +116,20 @@ module RuboCop
120
116
 
121
117
  match[2] ? 'find_by!' : 'find_by'
122
118
  end
119
+
120
+ def dynamic_find_by_arguments?(node)
121
+ dynamic_find_by_arguments_count?(node) && dynamic_find_by_arguments_type?(node)
122
+ end
123
+
124
+ def dynamic_find_by_arguments_count?(node)
125
+ column_keywords(node.method_name).size == node.arguments.size
126
+ end
127
+
128
+ def dynamic_find_by_arguments_type?(node)
129
+ node.arguments.none? do |argument|
130
+ IGNORED_ARGUMENT_TYPES.include?(argument.type)
131
+ end
132
+ end
123
133
  end
124
134
  end
125
135
  end
@@ -34,15 +34,15 @@ module RuboCop
34
34
  RESTRICT_ON_SEND = %i[join].freeze
35
35
 
36
36
  def_node_matcher :file_join_nodes?, <<~PATTERN
37
- (send (const nil? :File) :join ...)
37
+ (send (const {nil? cbase} :File) :join ...)
38
38
  PATTERN
39
39
 
40
40
  def_node_search :rails_root_nodes?, <<~PATTERN
41
- (send (const nil? :Rails) :root)
41
+ (send (const {nil? cbase} :Rails) :root)
42
42
  PATTERN
43
43
 
44
44
  def_node_matcher :rails_root_join_nodes?, <<~PATTERN
45
- (send (send (const nil? :Rails) :root) :join ...)
45
+ (send #rails_root_nodes? :join ...)
46
46
  PATTERN
47
47
 
48
48
  def on_dstr(node)
@@ -29,17 +29,17 @@ module RuboCop
29
29
 
30
30
  MSG = 'Use `freeze_time` instead of `travel_to`.'
31
31
  NOW_METHODS = %i[now new current].freeze
32
- CONV_METHODS = %i[to_time in_time_zone].freeze
32
+ CONVERT_METHODS = %i[to_time in_time_zone].freeze
33
33
  RESTRICT_ON_SEND = %i[travel_to].freeze
34
34
 
35
35
  # @!method time_now?(node)
36
36
  def_node_matcher :time_now?, <<~PATTERN
37
- (const nil? {:Time :DateTime})
37
+ (const {nil? cbase} {:Time :DateTime})
38
38
  PATTERN
39
39
 
40
40
  # @!method zoned_time_now?(node)
41
41
  def_node_matcher :zoned_time_now?, <<~PATTERN
42
- (send (const nil? :Time) :zone)
42
+ (send (const {nil? cbase} :Time) :zone)
43
43
  PATTERN
44
44
 
45
45
  def on_send(node)
@@ -63,9 +63,11 @@ module RuboCop
63
63
  end
64
64
 
65
65
  def current_time_with_convert?(node, method_name)
66
- return false unless CONV_METHODS.include?(method_name)
66
+ return false unless CONVERT_METHODS.include?(method_name)
67
+
68
+ child_node, child_method_name, time_argument = *node.children
69
+ return if time_argument
67
70
 
68
- child_node, child_method_name = *node.children
69
71
  current_time?(child_node, child_method_name)
70
72
  end
71
73
  end
@@ -37,7 +37,7 @@ module RuboCop
37
37
  RESTRICT_ON_SEND = %i[has_many has_one].freeze
38
38
 
39
39
  def_node_search :active_resource_class?, <<~PATTERN
40
- (const (const nil? :ActiveResource) :Base)
40
+ (const (const {nil? cbase} :ActiveResource) :Base)
41
41
  PATTERN
42
42
 
43
43
  def_node_matcher :association_without_options?, <<~PATTERN
@@ -37,7 +37,7 @@ module RuboCop
37
37
  def_node_matcher :form_builder_class?, <<~PATTERN
38
38
  (const
39
39
  (const
40
- (const nil? :ActionView) :Helpers) :FormBuilder)
40
+ (const {nil? cbase} :ActionView) :Helpers) :FormBuilder)
41
41
  PATTERN
42
42
 
43
43
  def on_ivar(node)
@@ -34,6 +34,8 @@ module RuboCop
34
34
 
35
35
  MSG = 'Use "lazy" lookup for the text used in controllers.'
36
36
 
37
+ RESTRICT_ON_SEND = %i[translate t].freeze
38
+
37
39
  def_node_matcher :translate_call?, <<~PATTERN
38
40
  (send nil? {:translate :t} ${sym_type? str_type?} ...)
39
41
  PATTERN
@@ -69,7 +69,7 @@ module RuboCop
69
69
  class I18nLocaleTexts < Base
70
70
  MSG = 'Move locale texts to the locale files in the `config/locales` directory.'
71
71
 
72
- RESTRICT_ON_SEND = %i[validates redirect_to []= mail].freeze
72
+ RESTRICT_ON_SEND = %i[validates redirect_to redirect_back []= mail].freeze
73
73
 
74
74
  def_node_search :validation_message, <<~PATTERN
75
75
  (pair (sym :message) $str)
@@ -94,7 +94,7 @@ module RuboCop
94
94
  add_offense(text_node)
95
95
  end
96
96
  return
97
- when :redirect_to
97
+ when :redirect_to, :redirect_back
98
98
  text_node = redirect_to_flash(node).to_a.last
99
99
  when :[]=
100
100
  text_node = flash_assignment?(node)
@@ -46,7 +46,7 @@ module RuboCop
46
46
 
47
47
  def_node_matcher :on_bad_hash_brackets_map, <<~PATTERN
48
48
  (send
49
- (const _ :Hash)
49
+ (const {nil? cbase} :Hash)
50
50
  :[]
51
51
  (block
52
52
  (call _ {:map :collect})
@@ -49,7 +49,7 @@ module RuboCop
49
49
 
50
50
  def_node_matcher :on_bad_hash_brackets_map, <<~PATTERN
51
51
  (send
52
- (const _ :Hash)
52
+ (const {nil? cbase} :Hash)
53
53
  :[]
54
54
  (block
55
55
  (call _ {:map :collect})
@@ -34,8 +34,8 @@ module RuboCop
34
34
 
35
35
  def_node_matcher :mailer_base_class?, <<~PATTERN
36
36
  {
37
- (const (const nil? :ActionMailer) :Base)
38
- (const nil? :ApplicationMailer)
37
+ (const (const {nil? cbase} :ActionMailer) :Base)
38
+ (const {nil? cbase} :ApplicationMailer)
39
39
  }
40
40
  PATTERN
41
41
 
@@ -44,7 +44,7 @@ module RuboCop
44
44
  PATTERN
45
45
 
46
46
  def_node_matcher :class_new_definition?, <<~PATTERN
47
- (send (const nil? :Class) :new #mailer_base_class?)
47
+ (send (const {nil? cbase} :Class) :new #mailer_base_class?)
48
48
  PATTERN
49
49
 
50
50
  def on_class(node)
@@ -29,7 +29,7 @@ module RuboCop
29
29
 
30
30
  basename = basename_without_timestamp_and_suffix(processed_source.file_path)
31
31
 
32
- class_identifier = node.identifier
32
+ class_identifier = node.identifier.location.name
33
33
  camelized_basename = camelize(basename)
34
34
  return if class_identifier.source.casecmp(camelized_basename).zero?
35
35
 
@@ -32,7 +32,7 @@ module RuboCop
32
32
  (send
33
33
  {
34
34
  (gvar #match_gvar?)
35
- {(const nil? :STDOUT) (const nil? :STDERR)}
35
+ (const {nil? cbase} {:STDOUT :STDERR})
36
36
  }
37
37
  {:binwrite :syswrite :write :write_nonblock}
38
38
  ...)
@@ -26,7 +26,7 @@ module RuboCop
26
26
  RESTRICT_ON_SEND = %i[require_dependency].freeze
27
27
 
28
28
  def_node_matcher :require_dependency_call?, <<~PATTERN
29
- (send {nil? (const _ :Kernel)} :require_dependency _)
29
+ (send {nil? (const {nil? cbase} :Kernel)} :require_dependency _)
30
30
  PATTERN
31
31
 
32
32
  def on_send(node)
@@ -163,12 +163,11 @@ module RuboCop
163
163
  def on_send(node)
164
164
  evidence(node) do |method, path, args, rails_root|
165
165
  add_offense(node, message: format(MSG, method: method, rails_root: rails_root.source)) do |corrector|
166
- if dir_glob?(node)
167
- replacement = build_path_glob(path, method)
168
- else
169
- replacement = "#{path.source}.#{method}"
170
- replacement += "(#{args.map(&:source).join(', ')})" unless args.empty?
171
- end
166
+ replacement = if dir_glob?(node)
167
+ build_path_glob_replacement(path, method)
168
+ else
169
+ build_path_replacement(path, method, args)
170
+ end
172
171
 
173
172
  corrector.replace(node, replacement)
174
173
  end
@@ -184,18 +183,26 @@ module RuboCop
184
183
  yield(method, path, args, rails_root)
185
184
  end
186
185
 
187
- def build_path_glob(path, method)
186
+ def build_path_glob_replacement(path, method)
188
187
  receiver = range_between(path.loc.expression.begin_pos, path.children.first.loc.selector.end_pos).source
189
188
 
190
- argument = if path.arguments.one?
191
- path.first_argument.source
192
- else
193
- join_arguments(path.arguments)
194
- end
189
+ argument = path.arguments.one? ? path.first_argument.source : join_arguments(path.arguments)
195
190
 
196
191
  "#{receiver}.#{method}(#{argument})"
197
192
  end
198
193
 
194
+ def build_path_replacement(path, method, args)
195
+ path_replacement = path.source
196
+ if path.arguments? && !path.parenthesized_call?
197
+ path_replacement[' '] = '('
198
+ path_replacement << ')'
199
+ end
200
+
201
+ replacement = "#{path_replacement}.#{method}"
202
+ replacement += "(#{args.map(&:source).join(', ')})" unless args.empty?
203
+ replacement
204
+ end
205
+
199
206
  def include_interpolation?(arguments)
200
207
  arguments.any? do |argument|
201
208
  argument.children.any? { |child| child.respond_to?(:begin_type?) && child.begin_type? }
@@ -203,11 +210,28 @@ module RuboCop
203
210
  end
204
211
 
205
212
  def join_arguments(arguments)
206
- quote = include_interpolation?(arguments) ? '"' : "'"
207
- joined_arguments = arguments.map(&:value).join('/')
213
+ use_interpolation = false
214
+
215
+ joined_arguments = arguments.map do |arg|
216
+ if arg.respond_to?(:value)
217
+ arg.value
218
+ else
219
+ use_interpolation = true
220
+ "\#{#{arg.source}}"
221
+ end
222
+ end.join('/')
223
+ quote = enforce_double_quotes? || include_interpolation?(arguments) || use_interpolation ? '"' : "'"
208
224
 
209
225
  "#{quote}#{joined_arguments}#{quote}"
210
226
  end
227
+
228
+ def enforce_double_quotes?
229
+ string_literals_config['EnforcedStyle'] == 'double_quotes'
230
+ end
231
+
232
+ def string_literals_config
233
+ config.for_cop('Style/StringLiterals')
234
+ end
211
235
  end
212
236
  end
213
237
  end
@@ -49,7 +49,7 @@ module RuboCop
49
49
  RESTRICT_ON_SEND = PREFERRED_METHODS.keys.freeze
50
50
 
51
51
  def_node_matcher :long_i18n?, <<~PATTERN
52
- (send {nil? (const nil? :I18n)} ${:translate :localize} ...)
52
+ (send {nil? (const {nil? cbase} :I18n)} ${:translate :localize} ...)
53
53
  PATTERN
54
54
 
55
55
  def on_send(node)
@@ -57,7 +57,7 @@ module RuboCop
57
57
 
58
58
  def_node_matcher :good_touch?, <<~PATTERN
59
59
  {
60
- (send (const nil? :FileUtils) :touch ...)
60
+ (send (const {nil? cbase} :FileUtils) :touch ...)
61
61
  (send _ :touch {true false})
62
62
  }
63
63
  PATTERN
@@ -228,12 +228,25 @@ module RuboCop
228
228
  acceptable
229
229
  end
230
230
 
231
- # Time.new can be called with a time zone offset
231
+ # Time.new, Time.at, and Time.now can be called with a time zone offset
232
232
  # When it is, that should be considered safe
233
233
  # Example:
234
234
  # Time.new(1988, 3, 15, 3, 0, 0, "-05:00")
235
235
  def offset_provided?(node)
236
- node.arguments.size >= 7
236
+ case node.method_name
237
+ when :new
238
+ node.arguments.size == 7 || offset_option_provided?(node)
239
+ when :at, :now
240
+ offset_option_provided?(node)
241
+ end
242
+ end
243
+
244
+ def offset_option_provided?(node)
245
+ options = node.last_argument
246
+ options&.hash_type? &&
247
+ options.each_pair.any? do |pair|
248
+ pair.key.sym_type? && pair.key.value == :in && !pair.value.nil_type?
249
+ end
237
250
  end
238
251
  end
239
252
  end
@@ -23,7 +23,7 @@ module RuboCop
23
23
  RESTRICT_ON_SEND = %i[zone=].freeze
24
24
 
25
25
  def_node_matcher :time_zone_assignment?, <<~PATTERN
26
- (send (const nil? :Time) :zone= ...)
26
+ (send (const {nil? cbase} :Time) :zone= ...)
27
27
  PATTERN
28
28
 
29
29
  def on_send(node)
@@ -21,6 +21,37 @@ module RuboCop
21
21
  extend AutoCorrector
22
22
  extend TargetRailsVersion
23
23
 
24
+ # These types are defined by the following files in ActiveSupport:
25
+ # lib/active_support/core_ext/array/conversions.rb
26
+ # lib/active_support/core_ext/date/conversions.rb
27
+ # lib/active_support/core_ext/date_time/conversions.rb
28
+ # lib/active_support/core_ext/numeric/conversions.rb
29
+ # lib/active_support/core_ext/range/conversions.rb
30
+ # lib/active_support/core_ext/time/conversions.rb
31
+ # lib/active_support/time_with_zone.rb
32
+ EXTENDED_FORMAT_TYPES = Set.new(
33
+ %i[
34
+ currency
35
+ db
36
+ delimited
37
+ human
38
+ human_size
39
+ inspect
40
+ iso8601
41
+ long
42
+ long_ordinal
43
+ nsec
44
+ number
45
+ percentage
46
+ phone
47
+ rfc822
48
+ rounded
49
+ short
50
+ time
51
+ usec
52
+ ]
53
+ )
54
+
24
55
  MSG = 'Use `to_formatted_s` instead.'
25
56
 
26
57
  RESTRICT_ON_SEND = %i[to_s].freeze
@@ -28,13 +59,19 @@ module RuboCop
28
59
  minimum_target_rails_version 7.0
29
60
 
30
61
  def on_send(node)
31
- return if node.arguments.empty?
62
+ return unless rails_extended_to_s?(node)
32
63
 
33
64
  add_offense(node.loc.selector) do |corrector|
34
65
  corrector.replace(node.loc.selector, 'to_formatted_s')
35
66
  end
36
67
  end
37
68
  alias on_csend on_send
69
+
70
+ private
71
+
72
+ def rails_extended_to_s?(node)
73
+ node.first_argument&.sym_type? && EXTENDED_FORMAT_TYPES.include?(node.first_argument.value)
74
+ end
38
75
  end
39
76
  end
40
77
  end
@@ -139,11 +139,20 @@ module RuboCop
139
139
  pairs = node.arguments.last
140
140
  return unless pairs.hash_type?
141
141
 
142
+ return true if condition_hash_part?(pairs, keys: %i[if unless])
143
+
144
+ uniqueness_node = uniqueness_part(node)
145
+ return unless uniqueness_node&.hash_type?
146
+
147
+ condition_hash_part?(uniqueness_node, keys: %i[if unless conditions])
148
+ end
149
+
150
+ def condition_hash_part?(pairs, keys:)
142
151
  pairs.each_pair.any? do |pair|
143
152
  key = pair.key
144
153
  next unless key.sym_type?
145
154
 
146
- key.value == :if || key.value == :unless
155
+ keys.include?(key.value)
147
156
  end
148
157
  end
149
158
 
@@ -38,7 +38,9 @@ module RuboCop
38
38
  def on_send(node)
39
39
  return unless node.first_argument.sym_type?
40
40
 
41
- where_node_and_argument(root_receiver(node)) do |where_node, where_argument|
41
+ root_receiver = root_receiver(node)
42
+ where_node_and_argument(root_receiver) do |where_node, where_argument|
43
+ next unless root_receiver == root_receiver(where_node)
42
44
  next unless same_relationship?(where_argument, node.first_argument)
43
45
 
44
46
  range = range_between(node.loc.selector.begin_pos, node.loc.expression.end_pos)
@@ -50,7 +52,12 @@ module RuboCop
50
52
  private
51
53
 
52
54
  def root_receiver(node)
53
- node&.parent&.send_type? ? root_receiver(node.parent) : node
55
+ parent = node.parent
56
+ if !parent&.send_type? || parent.method?(:or) || parent.method?(:and)
57
+ node
58
+ else
59
+ root_receiver(parent)
60
+ end
54
61
  end
55
62
 
56
63
  def same_relationship?(where, left_joins)
@@ -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.17.2'
7
+ STRING = '2.17.4'
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.17.2
4
+ version: 2.17.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bozhidar Batsov
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2022-10-27 00:00:00.000000000 Z
13
+ date: 2022-12-25 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: activesupport
@@ -235,7 +235,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
235
235
  - !ruby/object:Gem::Version
236
236
  version: '0'
237
237
  requirements: []
238
- rubygems_version: 3.2.22
238
+ rubygems_version: 3.3.26
239
239
  signing_key:
240
240
  specification_version: 4
241
241
  summary: Automatic Rails code style checking tool.