rubocop-rails 2.17.0 → 2.17.4

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 (41) hide show
  1. checksums.yaml +4 -4
  2. data/config/default.yml +4 -4
  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 +15 -10
  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/http_status.rb +1 -6
  22. data/lib/rubocop/cop/rails/i18n_lazy_lookup.rb +2 -0
  23. data/lib/rubocop/cop/rails/i18n_locale_texts.rb +2 -2
  24. data/lib/rubocop/cop/rails/index_by.rb +1 -1
  25. data/lib/rubocop/cop/rails/index_with.rb +1 -1
  26. data/lib/rubocop/cop/rails/mailer_name.rb +3 -3
  27. data/lib/rubocop/cop/rails/migration_class_name.rb +1 -1
  28. data/lib/rubocop/cop/rails/output.rb +1 -1
  29. data/lib/rubocop/cop/rails/pluck.rb +29 -10
  30. data/lib/rubocop/cop/rails/require_dependency.rb +1 -1
  31. data/lib/rubocop/cop/rails/root_pathname_methods.rb +38 -14
  32. data/lib/rubocop/cop/rails/short_i18n.rb +1 -1
  33. data/lib/rubocop/cop/rails/skips_model_validations.rb +1 -1
  34. data/lib/rubocop/cop/rails/time_zone.rb +15 -2
  35. data/lib/rubocop/cop/rails/time_zone_assignment.rb +1 -1
  36. data/lib/rubocop/cop/rails/to_s_with_argument.rb +38 -1
  37. data/lib/rubocop/cop/rails/unique_validation_without_index.rb +10 -1
  38. data/lib/rubocop/cop/rails/where_missing.rb +9 -2
  39. data/lib/rubocop/cop/rails/where_not_with_multiple_conditions.rb +1 -1
  40. data/lib/rubocop/rails/version.rb +1 -1
  41. metadata +3 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2a7a516c82d0953f3f1995b190ddc427aa251583287a9232229e82a218c93ba6
4
- data.tar.gz: f224a35df6de69c04c7c6f6314f9cfa713cc5accfba1bbce9d5554c7fd50a121
3
+ metadata.gz: 8a9beeaefa16cbe4afb5d598878444a835536ccda5d9b46c3e44e111513f1d8e
4
+ data.tar.gz: 669a9681ce5ab1fca038229a831f7e5acae6bbdcc3a58ecfc88d65fd54ef7190
5
5
  SHA512:
6
- metadata.gz: 5b3277765f3819855d3e29d3226c81ebc6231b1b1aaf583e1ae86db9e18b7b450bbc2b3f9bccaaa85623ca57e0ac139abde604c35bccc81c3081a80c4392f35d
7
- data.tar.gz: a3d7b79f10c2090b04a05d1b30cc3c639645aa6b3c2ad33247fecfa7597907238d8f6e727a8e0dd088b92d69842c488489e9883f9acfd4551e08a9b8f3dd1f8c
6
+ metadata.gz: 72ca38ec9a9136bfa5bd635db9290314a4d1a4c8e3942f15aa62a823885ff1a1783f884598dce6f52765db75daccf8e4054d3faecdf973da6f2c53b10dc2c153
7
+ data.tar.gz: 536c7d8f5dfec22444d2d0fc4b297eac18f7b87cc6c871ce250bec58a2a6d380e814749534e5aedc47c0a66c7ae80ac8c61ffec330093c575cd1cc8dd76e4de9
data/config/default.yml CHANGED
@@ -73,7 +73,7 @@ Rails/ActionControllerFlashBeforeRender:
73
73
  StyleGuide: 'https://rails.rubystyle.guide/#flash-before-render'
74
74
  Reference: 'https://api.rubyonrails.org/classes/ActionController/FlashBeforeRender.html'
75
75
  Enabled: 'pending'
76
- SafeAutocorrect: false
76
+ SafeAutoCorrect: false
77
77
  VersionAdded: '2.16'
78
78
 
79
79
  Rails/ActionControllerTestCase:
@@ -81,7 +81,7 @@ Rails/ActionControllerTestCase:
81
81
  StyleGuide: 'https://rails.rubystyle.guide/#integration-testing'
82
82
  Reference: 'https://api.rubyonrails.org/classes/ActionController/TestCase.html'
83
83
  Enabled: 'pending'
84
- SafeAutocorrect: false
84
+ SafeAutoCorrect: false
85
85
  VersionAdded: '2.14'
86
86
  Include:
87
87
  - '**/test/**/*.rb'
@@ -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.'
@@ -873,7 +873,7 @@ Rails/RootJoinChain:
873
873
  Rails/RootPathnameMethods:
874
874
  Description: 'Use `Rails.root` IO methods instead of passing it to `File`.'
875
875
  Enabled: pending
876
- SafeAutocorrect: false
876
+ SafeAutoCorrect: false
877
877
  VersionAdded: '2.16'
878
878
 
879
879
  Rails/RootPublicPath:
@@ -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
  :[]
@@ -37,14 +37,14 @@ module RuboCop
37
37
  ^(send (send nil? :flash) :[]= ...)
38
38
  PATTERN
39
39
 
40
- def_node_search :redirect_to?, <<~PATTERN
41
- (send nil? :redirect_to ...)
40
+ def_node_search :render?, <<~PATTERN
41
+ (send nil? :render ...)
42
42
  PATTERN
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
 
@@ -53,7 +53,7 @@ module RuboCop
53
53
  def on_send(flash_node)
54
54
  return unless flash_assignment?(flash_node)
55
55
 
56
- return if followed_by_redirect_to?(flash_node)
56
+ return unless followed_by_render?(flash_node)
57
57
 
58
58
  return unless instance_method_or_block?(flash_node)
59
59
 
@@ -66,13 +66,18 @@ module RuboCop
66
66
 
67
67
  private
68
68
 
69
- def followed_by_redirect_to?(flash_node)
69
+ def followed_by_render?(flash_node)
70
70
  flash_assigment_node = find_ancestor(flash_node, type: :send)
71
- context = flash_assigment_node.parent
71
+ context = flash_assigment_node
72
+ if (node = context.each_ancestor(:if, :rescue).first)
73
+ context = node
74
+ elsif context.right_siblings.empty?
75
+ return true
76
+ end
77
+ context = context.right_siblings
72
78
 
73
- flash_index = context.children.index(flash_assigment_node)
74
- context.each_child_node.with_index.any? do |node, index|
75
- index > flash_index && redirect_to?(node)
79
+ context.compact.any? do |render_candidate|
80
+ render?(render_candidate)
76
81
  end
77
82
  end
78
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)
@@ -12,7 +12,6 @@ module RuboCop
12
12
  # render plain: 'foo/bar', status: 304
13
13
  # redirect_to root_url, status: 301
14
14
  # head 200
15
- # get '/foobar', to: redirect('/foobar/baz', status: 301)
16
15
  #
17
16
  # # good
18
17
  # render :foo, status: :ok
@@ -20,7 +19,6 @@ module RuboCop
20
19
  # render plain: 'foo/bar', status: :not_modified
21
20
  # redirect_to root_url, status: :moved_permanently
22
21
  # head :ok
23
- # get '/foobar', to: redirect('/foobar/baz', status: :moved_permanently)
24
22
  #
25
23
  # @example EnforcedStyle: numeric
26
24
  # # bad
@@ -29,7 +27,6 @@ module RuboCop
29
27
  # render plain: 'foo/bar', status: :not_modified
30
28
  # redirect_to root_url, status: :moved_permanently
31
29
  # head :ok
32
- # get '/foobar', to: redirect('/foobar/baz', status: :moved_permanently)
33
30
  #
34
31
  # # good
35
32
  # render :foo, status: 200
@@ -37,20 +34,18 @@ module RuboCop
37
34
  # render plain: 'foo/bar', status: 304
38
35
  # redirect_to root_url, status: 301
39
36
  # head 200
40
- # get '/foobar', to: redirect('/foobar/baz', status: 301)
41
37
  #
42
38
  class HttpStatus < Base
43
39
  include ConfigurableEnforcedStyle
44
40
  extend AutoCorrector
45
41
 
46
- RESTRICT_ON_SEND = %i[render redirect_to head redirect].freeze
42
+ RESTRICT_ON_SEND = %i[render redirect_to head].freeze
47
43
 
48
44
  def_node_matcher :http_status, <<~PATTERN
49
45
  {
50
46
  (send nil? {:render :redirect_to} _ $hash)
51
47
  (send nil? {:render :redirect_to} $hash)
52
48
  (send nil? :head ${int sym} ...)
53
- (send nil? :redirect _ $hash)
54
49
  }
55
50
  PATTERN
56
51
 
@@ -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,34 +26,53 @@ module RuboCop
26
26
  minimum_target_rails_version 5.0
27
27
 
28
28
  def_node_matcher :pluck_candidate?, <<~PATTERN
29
- ({block numblock} (send _ {:map :collect}) $_argument (send (lvar $_element) :[] $_key))
29
+ ({block numblock} (send _ {:map :collect}) $_argument (send lvar :[] $_key))
30
30
  PATTERN
31
31
 
32
32
  def on_block(node)
33
- pluck_candidate?(node) do |argument, element, key|
33
+ pluck_candidate?(node) do |argument, key|
34
+ next unless use_one_block_argument?(argument)
35
+
34
36
  match = if node.block_type?
35
- argument.children.first.source.to_sym == element
37
+ block_argument = argument.children.first.source
38
+ use_block_argument_in_key?(block_argument, key)
36
39
  else # numblock
37
- argument == 1 && element == :_1
40
+ argument == 1 && use_block_argument_in_key?('_1', key)
38
41
  end
39
42
  next unless match
40
43
 
41
- replacement = "pluck(#{key.source})"
42
- message = message(replacement, node)
43
-
44
- add_offense(offense_range(node), message: message) do |corrector|
45
- corrector.replace(offense_range(node), replacement)
46
- end
44
+ register_offense(node, key)
47
45
  end
48
46
  end
49
47
  alias on_numblock on_block
50
48
 
51
49
  private
52
50
 
51
+ def use_one_block_argument?(argument)
52
+ return true if argument == 1 # Checks for numbered argument `_1`.
53
+
54
+ argument.respond_to?(:one?) && argument.one?
55
+ end
56
+
57
+ def use_block_argument_in_key?(block_argument, key)
58
+ return false if block_argument == key.source
59
+
60
+ key.each_descendant(:lvar).none? { |lvar| block_argument == lvar.source }
61
+ end
62
+
53
63
  def offense_range(node)
54
64
  node.send_node.loc.selector.join(node.loc.end)
55
65
  end
56
66
 
67
+ def register_offense(node, key)
68
+ replacement = "pluck(#{key.source})"
69
+ message = message(replacement, node)
70
+
71
+ add_offense(offense_range(node), message: message) do |corrector|
72
+ corrector.replace(offense_range(node), replacement)
73
+ end
74
+ end
75
+
57
76
  def message(replacement, node)
58
77
  current = offense_range(node).source
59
78
 
@@ -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)
@@ -45,7 +45,7 @@ module RuboCop
45
45
 
46
46
  def multiple_arguments_hash?(hash)
47
47
  return true if hash.pairs.size >= 2
48
- return false unless hash.values[0].hash_type?
48
+ return false unless hash.values[0]&.hash_type?
49
49
 
50
50
  multiple_arguments_hash?(hash.values[0])
51
51
  end
@@ -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.0'
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.0
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-22 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.