rubocop-rails 2.24.1 → 2.25.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ee7d9bdd0fa7838d8bfecece133ccd92cc308603bcdf120c14224c5f2e312344
4
- data.tar.gz: 7d740b495ffa368d26a73808af9adff990bb9c1d317d905f877b4f3e2025487c
3
+ metadata.gz: b55703b258e4df9bae3a9a8c1b77a4fa143af6af3ed9b1b22118fd839d1ce06c
4
+ data.tar.gz: 24568d7d8d22d69469ae9a4aaf426ea45bf848989604a8744ab6493bc0b222a3
5
5
  SHA512:
6
- metadata.gz: 98172f41e7b1aab55931f351757425815850809edd6fc4f5bc55c59271d9f12459ac51c2002e409534b623f321ab27798c32aaccb2d15d60c7bf309a9c044d58
7
- data.tar.gz: c78e9a0d9e53e5203a2dd1846a79c3e9c7a16af69e98d94e6b7ae6358ae6a29b71c2ac3f09ddb2a9be06dc64097a736c3379730f50b9309c0e2ae084267aee70
6
+ metadata.gz: 47d5668b744967fc740b47552e9d05068fc5ce5c209a4a994a429262a0628b13e0f1e48d6a39018430fb7e874f88e4badbd9427219b9a77220e522d6706b1c3c
7
+ data.tar.gz: 242030d6fe9063d51f18e98869e2a048a4b7b847f0f6a25937fc3d9781dbb7749de8b015b3fa0b4933511b83487ad060fbdbf9adb7942a20bfc035d5fc9e2f04
data/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # RuboCop Rails
2
2
 
3
3
  [![Gem Version](https://badge.fury.io/rb/rubocop-rails.svg)](https://badge.fury.io/rb/rubocop-rails)
4
- [![CircleCI](https://circleci.com/gh/rubocop/rubocop-rails.svg?style=svg)](https://circleci.com/gh/rubocop/rubocop-rails)
4
+ [![CI](https://github.com/rubocop/rubocop-rails/actions/workflows/test.yml/badge.svg)](https://github.com/rubocop/rubocop-rails/actions/workflows/test.yml)
5
5
 
6
6
  A [RuboCop](https://github.com/rubocop/rubocop) extension focused on enforcing Rails best practices and coding conventions.
7
7
 
data/config/default.yml CHANGED
@@ -693,7 +693,7 @@ Rails/NegateInclude:
693
693
  VersionChanged: '2.9'
694
694
 
695
695
  Rails/NotNullColumn:
696
- Description: 'Do not add a NOT NULL column without a default value.'
696
+ Description: 'Do not add a NOT NULL column without a default value to existing tables.'
697
697
  Enabled: true
698
698
  VersionAdded: '0.43'
699
699
  VersionChanged: '2.20'
@@ -1018,8 +1018,9 @@ Rails/SkipsModelValidations:
1018
1018
  See reference for more information.
1019
1019
  Reference: 'https://guides.rubyonrails.org/active_record_validations.html#skipping-validations'
1020
1020
  Enabled: true
1021
+ Safe: false
1021
1022
  VersionAdded: '0.47'
1022
- VersionChanged: '2.7'
1023
+ VersionChanged: '2.25'
1023
1024
  ForbiddenMethods:
1024
1025
  - decrement!
1025
1026
  - decrement_counter
@@ -1163,8 +1164,9 @@ Rails/UnknownEnv:
1163
1164
 
1164
1165
  Rails/UnusedIgnoredColumns:
1165
1166
  Description: 'Remove a column that does not exist from `ignored_columns`.'
1166
- Enabled: pending
1167
+ Enabled: false
1167
1168
  VersionAdded: '2.11'
1169
+ VersionChanged: '2.25'
1168
1170
  Include:
1169
1171
  - app/models/**/*.rb
1170
1172
 
@@ -1221,6 +1223,13 @@ Rails/WhereNotWithMultipleConditions:
1221
1223
  VersionAdded: '2.17'
1222
1224
  VersionChanged: '2.18'
1223
1225
 
1226
+ Rails/WhereRange:
1227
+ Description: 'Use ranges in `where` instead of manually constructing SQL.'
1228
+ StyleGuide: 'https://rails.rubystyle.guide/#where-ranges'
1229
+ Enabled: pending
1230
+ SafeAutoCorrect: false
1231
+ VersionAdded: '2.25'
1232
+
1224
1233
  # Accept `redirect_to(...) and return` and similar cases.
1225
1234
  Style/AndOr:
1226
1235
  EnforcedStyle: conditionals
@@ -4,13 +4,40 @@ module RuboCop
4
4
  module Cop
5
5
  # Common functionality for checking target rails version.
6
6
  module TargetRailsVersion
7
+ # Informs the base RuboCop gem that it the Rails version is checked via `requires_gem` API,
8
+ # without needing to call this `#support_target_rails_version` method.
9
+ USES_REQUIRES_GEM_API = true
10
+
7
11
  def minimum_target_rails_version(version)
8
- @minimum_target_rails_version = version
12
+ if respond_to?(:requires_gem)
13
+ case version
14
+ when Integer, Float then requires_gem(TARGET_GEM_NAME, ">= #{version}")
15
+ when String then requires_gem(TARGET_GEM_NAME, version)
16
+ end
17
+ else
18
+ # Fallback path for previous versions of RuboCop which don't support the `requires_gem` API yet.
19
+ @minimum_target_rails_version = version
20
+ end
9
21
  end
10
22
 
11
23
  def support_target_rails_version?(version)
12
- @minimum_target_rails_version <= version
24
+ if respond_to?(:requires_gem)
25
+ return false unless gem_requirements
26
+
27
+ gem_requirement = gem_requirements[TARGET_GEM_NAME]
28
+ return true unless gem_requirement # If we have no requirement, then we support all versions
29
+
30
+ gem_requirement.satisfied_by?(Gem::Version.new(version))
31
+ else
32
+ # Fallback path for previous versions of RuboCop which don't support the `requires_gem` API yet.
33
+ @minimum_target_rails_version <= version
34
+ end
13
35
  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
14
41
  end
15
42
  end
16
43
  end
@@ -113,8 +113,10 @@ module RuboCop
113
113
  MYSQL_COMBINABLE_ALTER_METHODS = %i[rename_column add_index remove_index].freeze
114
114
 
115
115
  POSTGRESQL_COMBINABLE_TRANSFORMATIONS = %i[change_default].freeze
116
+ POSTGRESQL_COMBINABLE_TRANSFORMATIONS_SINCE_6_1 = %i[change_null].freeze
116
117
 
117
118
  POSTGRESQL_COMBINABLE_ALTER_METHODS = %i[change_column_default].freeze
119
+ POSTGRESQL_COMBINABLE_ALTER_METHODS_SINCE_6_1 = %i[change_column_null].freeze
118
120
 
119
121
  def on_def(node)
120
122
  return unless support_bulk_alter?
@@ -196,7 +198,9 @@ module RuboCop
196
198
  when MYSQL
197
199
  COMBINABLE_ALTER_METHODS + MYSQL_COMBINABLE_ALTER_METHODS
198
200
  when POSTGRESQL
199
- COMBINABLE_ALTER_METHODS + POSTGRESQL_COMBINABLE_ALTER_METHODS
201
+ result = COMBINABLE_ALTER_METHODS + POSTGRESQL_COMBINABLE_ALTER_METHODS
202
+ result += POSTGRESQL_COMBINABLE_ALTER_METHODS_SINCE_6_1 if target_rails_version >= 6.1
203
+ result
200
204
  end
201
205
  end
202
206
 
@@ -205,7 +209,9 @@ module RuboCop
205
209
  when MYSQL
206
210
  COMBINABLE_TRANSFORMATIONS + MYSQL_COMBINABLE_TRANSFORMATIONS
207
211
  when POSTGRESQL
208
- COMBINABLE_TRANSFORMATIONS + POSTGRESQL_COMBINABLE_TRANSFORMATIONS
212
+ result = COMBINABLE_TRANSFORMATIONS + POSTGRESQL_COMBINABLE_TRANSFORMATIONS
213
+ result += POSTGRESQL_COMBINABLE_TRANSFORMATIONS_SINCE_6_1 if target_rails_version >= 6.1
214
+ result
209
215
  end
210
216
  end
211
217
 
@@ -13,6 +13,8 @@ module RuboCop
13
13
  # render plain: 'foo/bar', status: 304
14
14
  # redirect_to root_url, status: 301
15
15
  # head 200
16
+ # assert_response 200
17
+ # assert_redirected_to '/some/path', status: 301
16
18
  #
17
19
  # # good
18
20
  # render :foo, status: :ok
@@ -20,6 +22,8 @@ module RuboCop
20
22
  # render plain: 'foo/bar', status: :not_modified
21
23
  # redirect_to root_url, status: :moved_permanently
22
24
  # head :ok
25
+ # assert_response :ok
26
+ # assert_redirected_to '/some/path', status: :moved_permanently
23
27
  #
24
28
  # @example EnforcedStyle: numeric
25
29
  # # bad
@@ -28,6 +32,8 @@ module RuboCop
28
32
  # render plain: 'foo/bar', status: :not_modified
29
33
  # redirect_to root_url, status: :moved_permanently
30
34
  # head :ok
35
+ # assert_response :ok
36
+ # assert_redirected_to '/some/path', status: :moved_permanently
31
37
  #
32
38
  # # good
33
39
  # render :foo, status: 200
@@ -35,18 +41,22 @@ module RuboCop
35
41
  # render plain: 'foo/bar', status: 304
36
42
  # redirect_to root_url, status: 301
37
43
  # head 200
44
+ # assert_response 200
45
+ # assert_redirected_to '/some/path', status: 301
38
46
  #
39
47
  class HttpStatus < Base
40
48
  include ConfigurableEnforcedStyle
41
49
  extend AutoCorrector
42
50
 
43
- RESTRICT_ON_SEND = %i[render redirect_to head].freeze
51
+ RESTRICT_ON_SEND = %i[render redirect_to head assert_response assert_redirected_to].freeze
44
52
 
45
53
  def_node_matcher :http_status, <<~PATTERN
46
54
  {
47
55
  (send nil? {:render :redirect_to} _ $hash)
48
56
  (send nil? {:render :redirect_to} $hash)
49
- (send nil? :head ${int sym} ...)
57
+ (send nil? {:head :assert_response} ${int sym} ...)
58
+ (send nil? :assert_redirected_to _ $hash ...)
59
+ (send nil? :assert_redirected_to $hash ...)
50
60
  }
51
61
  PATTERN
52
62
 
@@ -3,7 +3,7 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Rails
6
- # Checks for calls to `link_to` that contain a
6
+ # Checks for calls to `link_to`, `link_to_if`, and `link_to_unless` methods that contain a
7
7
  # `target: '_blank'` but no `rel: 'noopener'`. This can be a security
8
8
  # risk as the loaded page will have control over the previous page
9
9
  # and could change its location for phishing purposes.
@@ -24,7 +24,7 @@ module RuboCop
24
24
  extend AutoCorrector
25
25
 
26
26
  MSG = 'Specify a `:rel` option containing noopener.'
27
- RESTRICT_ON_SEND = %i[link_to].freeze
27
+ RESTRICT_ON_SEND = %i[link_to link_to_if link_to_unless].freeze
28
28
 
29
29
  def_node_matcher :blank_target?, <<~PATTERN
30
30
  (pair {(sym :target) (str "target")} {(str "_blank") (sym :_blank)})
@@ -3,24 +3,42 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Rails
6
- # Checks for add_column call with NOT NULL constraint in migration file.
6
+ # Checks for add_column calls with a NOT NULL constraint without a default
7
+ # value.
7
8
  #
8
- # `TEXT` can have default values in PostgreSQL, but not in MySQL.
9
- # It will automatically detect an adapter from `development` environment
10
- # in `config/database.yml` or the environment variable `DATABASE_URL`
11
- # when the `Database` option is not set. If the database is MySQL,
12
- # this cop ignores offenses for the `TEXT`.
9
+ # This cop only applies when adding a column to an existing table, since
10
+ # existing records will not have a value for the new column. New tables
11
+ # can freely use NOT NULL columns without defaults, since there are no
12
+ # records that could violate the constraint.
13
+ #
14
+ # If you need to add a NOT NULL column to an existing table, you must add
15
+ # it as nullable first, back-fill the data, and then use
16
+ # `change_column_null`. Alternatively, you could add the column with a
17
+ # default first to have the database automatically backfill existing rows,
18
+ # and then use `change_column_default` to remove the default.
19
+ #
20
+ # `TEXT` cannot have a default value in MySQL.
21
+ # The cop will automatically detect an adapter from `development`
22
+ # environment in `config/database.yml` or the environment variable
23
+ # `DATABASE_URL` when the `Database` option is not set. If the database
24
+ # is MySQL, this cop ignores offenses for `TEXT` columns.
13
25
  #
14
26
  # @example
15
27
  # # bad
16
28
  # add_column :users, :name, :string, null: false
17
29
  # add_reference :products, :category, null: false
30
+ # change_table :users do |t|
31
+ # t.string :name, null: false
32
+ # end
18
33
  #
19
34
  # # good
20
35
  # add_column :users, :name, :string, null: true
21
36
  # add_column :users, :name, :string, null: false, default: ''
37
+ # change_table :users do |t|
38
+ # t.string :name, null: false, default: ''
39
+ # end
22
40
  # add_reference :products, :category
23
- # add_reference :products, :category, null: false, default: 1
41
+ # change_column_null :products, :category_id, false
24
42
  class NotNullColumn < Base
25
43
  include DatabaseTypeResolvable
26
44
 
@@ -35,6 +53,22 @@ module RuboCop
35
53
  (send nil? :add_reference _ _ (hash $...))
36
54
  PATTERN
37
55
 
56
+ def_node_matcher :change_table?, <<~PATTERN
57
+ (block (send nil? :change_table ...) (args (arg $_)) _)
58
+ PATTERN
59
+
60
+ def_node_matcher :add_not_null_column_in_change_table?, <<~PATTERN
61
+ (send (lvar $_) :column _ $_ (hash $...))
62
+ PATTERN
63
+
64
+ def_node_matcher :add_not_null_column_via_shortcut_in_change_table?, <<~PATTERN
65
+ (send (lvar $_) $_ _ (hash $...))
66
+ PATTERN
67
+
68
+ def_node_matcher :add_not_null_reference_in_change_table?, <<~PATTERN
69
+ (send (lvar $_) :add_reference _ _ (hash $...))
70
+ PATTERN
71
+
38
72
  def_node_matcher :null_false?, <<~PATTERN
39
73
  (pair (sym :null) (false))
40
74
  PATTERN
@@ -48,16 +82,25 @@ module RuboCop
48
82
  check_add_reference(node)
49
83
  end
50
84
 
85
+ def on_block(node)
86
+ check_change_table(node)
87
+ end
88
+ alias on_numblock on_block
89
+
51
90
  private
52
91
 
92
+ def check_column(type, pairs)
93
+ if type.respond_to?(:value)
94
+ return if type.value == :virtual || type.value == 'virtual'
95
+ return if (type.value == :text || type.value == 'text') && database == MYSQL
96
+ end
97
+
98
+ check_pairs(pairs)
99
+ end
100
+
53
101
  def check_add_column(node)
54
102
  add_not_null_column?(node) do |type, pairs|
55
- if type.respond_to?(:value)
56
- return if type.value == :virtual || type.value == 'virtual'
57
- return if (type.value == :text || type.value == 'text') && database == MYSQL
58
- end
59
-
60
- check_pairs(pairs)
103
+ check_column(type, pairs)
61
104
  end
62
105
  end
63
106
 
@@ -67,6 +110,43 @@ module RuboCop
67
110
  end
68
111
  end
69
112
 
113
+ def check_add_column_in_change_table(node, table)
114
+ add_not_null_column_in_change_table?(node) do |receiver, type, pairs|
115
+ next unless receiver == table
116
+
117
+ check_column(type, pairs)
118
+ end
119
+ end
120
+
121
+ def check_add_column_via_shortcut_in_change_table(node, table)
122
+ add_not_null_column_via_shortcut_in_change_table?(node) do |receiver, type, pairs|
123
+ next unless receiver == table
124
+
125
+ check_column(type, pairs)
126
+ end
127
+ end
128
+
129
+ def check_add_reference_in_change_table(node, table)
130
+ add_not_null_reference_in_change_table?(node) do |receiver, pairs|
131
+ next unless receiver == table
132
+
133
+ check_pairs(pairs)
134
+ end
135
+ end
136
+
137
+ def check_change_table(node)
138
+ change_table?(node) do |table|
139
+ next unless node.body
140
+
141
+ children = node.body.begin_type? ? node.body.children : [node.body]
142
+ children.each do |child|
143
+ check_add_column_in_change_table(child, table)
144
+ check_add_column_via_shortcut_in_change_table(child, table)
145
+ check_add_reference_in_change_table(child, table)
146
+ end
147
+ end
148
+ end
149
+
70
150
  def check_pairs(pairs)
71
151
  return if pairs.any? { |pair| default_option?(pair) }
72
152
 
@@ -9,6 +9,10 @@ module RuboCop
9
9
  # `pick` avoids. When called on an Active Record relation, `pick` adds a
10
10
  # limit to the query so that only one value is fetched from the database.
11
11
  #
12
+ # Note that when `pick` is added to a relation with an existing limit, it
13
+ # causes a subquery to be added. In most cases this is undesirable, and
14
+ # care should be taken while resolving this violation.
15
+ #
12
16
  # @safety
13
17
  # This cop is unsafe because `pluck` is defined on both `ActiveRecord::Relation` and `Enumerable`,
14
18
  # whereas `pick` is only defined on `ActiveRecord::Relation` in Rails 6.0. This was addressed
@@ -9,6 +9,9 @@ module RuboCop
9
9
  #
10
10
  # Methods may be ignored from this rule by configuring a `AllowedMethods`.
11
11
  #
12
+ # @safety
13
+ # This cop is unsafe if the receiver object is not an Active Record object.
14
+ #
12
15
  # @example
13
16
  # # bad
14
17
  # Article.first.decrement!(:view_count)
@@ -63,7 +66,7 @@ module RuboCop
63
66
  PATTERN
64
67
 
65
68
  def_node_matcher :good_insert?, <<~PATTERN
66
- (send _ {:insert :insert!} _ {
69
+ (call _ {:insert :insert!} _ {
67
70
  !(hash ...)
68
71
  (hash <(pair (sym !{:returning :unique_by}) _) ...>)
69
72
  } ...)
@@ -87,7 +87,7 @@ module RuboCop
87
87
 
88
88
  def environments
89
89
  @environments ||= begin
90
- environments = cop_config['Environments'] || []
90
+ environments = cop_config['Environments'].dup || []
91
91
  environments << 'local' if target_rails_version >= 7.1
92
92
  environments
93
93
  end
@@ -7,6 +7,12 @@ module RuboCop
7
7
  # `ignored_columns` is necessary to drop a column from RDBMS, but you don't need it after the migration
8
8
  # to drop the column. You avoid forgetting to remove `ignored_columns` by this cop.
9
9
  #
10
+ # IMPORTANT: This cop can't be used to effectively check for unused columns because the development
11
+ # and production schema can be out of sync until the migration has been run on production. As such,
12
+ # this cop can cause `ignored_columns` to be removed even though the production schema still contains
13
+ # the column, which can lead to downtime when the migration is actually executed. Only enable this cop
14
+ # if you know your migrations will be run before any of your Rails applications boot with the modified code.
15
+ #
10
16
  # @example
11
17
  # # bad
12
18
  # class User < ApplicationRecord
@@ -8,6 +8,7 @@ module RuboCop
8
8
  # @example
9
9
  # # bad
10
10
  # validates_acceptance_of :foo
11
+ # validates_comparison_of :foo
11
12
  # validates_confirmation_of :foo
12
13
  # validates_exclusion_of :foo
13
14
  # validates_format_of :foo
@@ -22,6 +23,7 @@ module RuboCop
22
23
  # # good
23
24
  # validates :foo, acceptance: true
24
25
  # validates :foo, confirmation: true
26
+ # validates :foo, comparison: true
25
27
  # validates :foo, exclusion: true
26
28
  # validates :foo, format: true
27
29
  # validates :foo, inclusion: true
@@ -29,7 +31,7 @@ module RuboCop
29
31
  # validates :foo, numericality: true
30
32
  # validates :foo, presence: true
31
33
  # validates :foo, absence: true
32
- # validates :foo, size: true
34
+ # validates :foo, length: true
33
35
  # validates :foo, uniqueness: true
34
36
  #
35
37
  class Validation < Base
@@ -39,6 +41,7 @@ module RuboCop
39
41
 
40
42
  TYPES = %w[
41
43
  acceptance
44
+ comparison
42
45
  confirmation
43
46
  exclusion
44
47
  format
@@ -120,7 +123,9 @@ module RuboCop
120
123
  end
121
124
 
122
125
  def validate_type(node)
123
- node.method_name.to_s.split('_')[1]
126
+ type = node.method_name.to_s.split('_')[1]
127
+
128
+ type == 'size' ? 'length' : type
124
129
  end
125
130
 
126
131
  def frozen_array_argument?(argument)
@@ -0,0 +1,198 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
6
+ # Identifies places where manually constructed SQL
7
+ # in `where` can be replaced with ranges.
8
+ #
9
+ # @safety
10
+ # This cop's autocorrection is unsafe because it can change the query
11
+ # by explicitly attaching the column to the wrong table.
12
+ # For example, `Booking.joins(:events).where('end_at < ?', Time.current)` will correctly
13
+ # implicitly attach the `end_at` column to the `events` table. But when autocorrected to
14
+ # `Booking.joins(:events).where(end_at: ...Time.current)`, it will now be incorrectly
15
+ # explicitly attached to the `bookings` table.
16
+ #
17
+ # @example
18
+ # # bad
19
+ # User.where('age >= ?', 18)
20
+ # User.where.not('age >= ?', 18)
21
+ # User.where('age < ?', 18)
22
+ # User.where('age >= ? AND age < ?', 18, 21)
23
+ # User.where('age >= :start', start: 18)
24
+ # User.where('users.age >= ?', 18)
25
+ #
26
+ # # good
27
+ # User.where(age: 18..)
28
+ # User.where.not(age: 18..)
29
+ # User.where(age: ...18)
30
+ # User.where(age: 18...21)
31
+ # User.where(users: { age: 18.. })
32
+ #
33
+ # # good
34
+ # # There are no beginless ranges in ruby.
35
+ # User.where('age > ?', 18)
36
+ #
37
+ class WhereRange < Base
38
+ include RangeHelp
39
+ extend AutoCorrector
40
+ extend TargetRubyVersion
41
+ extend TargetRailsVersion
42
+
43
+ MSG = 'Use `%<good_method>s` instead of manually constructing SQL.'
44
+
45
+ RESTRICT_ON_SEND = %i[where not].freeze
46
+
47
+ # column >= ?
48
+ GTEQ_ANONYMOUS_RE = /\A\s*([\w.]+)\s+>=\s+\?\s*\z/.freeze
49
+ # column <[=] ?
50
+ LTEQ_ANONYMOUS_RE = /\A\s*([\w.]+)\s+(<=?)\s+\?\s*\z/.freeze
51
+ # column >= ? AND column <[=] ?
52
+ RANGE_ANONYMOUS_RE = /\A\s*([\w.]+)\s+>=\s+\?\s+AND\s+\1\s+(<=?)\s+\?\s*\z/i.freeze
53
+ # column >= :value
54
+ GTEQ_NAMED_RE = /\A\s*([\w.]+)\s+>=\s+:(\w+)\s*\z/.freeze
55
+ # column <[=] :value
56
+ LTEQ_NAMED_RE = /\A\s*([\w.]+)\s+(<=?)\s+:(\w+)\s*\z/.freeze
57
+ # column >= :value1 AND column <[=] :value2
58
+ RANGE_NAMED_RE = /\A\s*([\w.]+)\s+>=\s+:(\w+)\s+AND\s+\1\s+(<=?)\s+:(\w+)\s*\z/i.freeze
59
+
60
+ minimum_target_ruby_version 2.6
61
+ minimum_target_rails_version 6.0
62
+
63
+ def_node_matcher :where_range_call?, <<~PATTERN
64
+ {
65
+ (call _ {:where :not} (array $str_type? $_ +))
66
+ (call _ {:where :not} $str_type? $_ +)
67
+ }
68
+ PATTERN
69
+
70
+ def on_send(node)
71
+ return if node.method?(:not) && !where_not?(node)
72
+
73
+ where_range_call?(node) do |template_node, values_node|
74
+ column, value = extract_column_and_value(template_node, values_node)
75
+
76
+ return unless column
77
+
78
+ range = offense_range(node)
79
+ good_method = build_good_method(node.method_name, column, value)
80
+ message = format(MSG, good_method: good_method)
81
+
82
+ add_offense(range, message: message) do |corrector|
83
+ corrector.replace(range, good_method)
84
+ end
85
+ end
86
+ end
87
+
88
+ private
89
+
90
+ def where_not?(node)
91
+ receiver = node.receiver
92
+ receiver&.send_type? && receiver&.method?(:where)
93
+ end
94
+
95
+ # rubocop:disable Metrics
96
+ def extract_column_and_value(template_node, values_node)
97
+ case template_node.value
98
+ when GTEQ_ANONYMOUS_RE
99
+ lhs = values_node[0]
100
+ operator = '..'
101
+ when LTEQ_ANONYMOUS_RE
102
+ if target_ruby_version >= 2.7
103
+ operator = range_operator(Regexp.last_match(2))
104
+ rhs = values_node[0]
105
+ end
106
+ when RANGE_ANONYMOUS_RE
107
+ if values_node.size >= 2
108
+ lhs = values_node[0]
109
+ operator = range_operator(Regexp.last_match(2))
110
+ rhs = values_node[1]
111
+ end
112
+ when GTEQ_NAMED_RE
113
+ value_node = values_node[0]
114
+
115
+ if value_node.hash_type?
116
+ pair = find_pair(value_node, Regexp.last_match(2))
117
+ lhs = pair.value
118
+ operator = '..'
119
+ end
120
+ when LTEQ_NAMED_RE
121
+ value_node = values_node[0]
122
+
123
+ if value_node.hash_type?
124
+ pair = find_pair(value_node, Regexp.last_match(2))
125
+ if pair && target_ruby_version >= 2.7
126
+ operator = range_operator(Regexp.last_match(2))
127
+ rhs = pair.value
128
+ end
129
+ end
130
+ when RANGE_NAMED_RE
131
+ value_node = values_node[0]
132
+
133
+ if value_node.hash_type?
134
+ pair1 = find_pair(value_node, Regexp.last_match(2))
135
+ pair2 = find_pair(value_node, Regexp.last_match(4))
136
+
137
+ if pair1 && pair2
138
+ lhs = pair1.value
139
+ operator = range_operator(Regexp.last_match(3))
140
+ rhs = pair2.value
141
+ end
142
+ end
143
+ end
144
+
145
+ if lhs
146
+ lhs_source = parentheses_needed?(lhs) ? "(#{lhs.source})" : lhs.source
147
+ end
148
+
149
+ if rhs
150
+ rhs_source = parentheses_needed?(rhs) ? "(#{rhs.source})" : rhs.source
151
+ end
152
+
153
+ [Regexp.last_match(1), "#{lhs_source}#{operator}#{rhs_source}"] if operator
154
+ end
155
+ # rubocop:enable Metrics
156
+
157
+ def range_operator(comparison_operator)
158
+ comparison_operator == '<' ? '...' : '..'
159
+ end
160
+
161
+ def find_pair(hash_node, value)
162
+ hash_node.pairs.find { |pair| pair.key.value.to_sym == value.to_sym }
163
+ end
164
+
165
+ def offense_range(node)
166
+ range_between(node.loc.selector.begin_pos, node.source_range.end_pos)
167
+ end
168
+
169
+ def build_good_method(method_name, column, value)
170
+ if column.include?('.')
171
+ table, column = column.split('.')
172
+
173
+ "#{method_name}(#{table}: { #{column}: #{value} })"
174
+ else
175
+ "#{method_name}(#{column}: #{value})"
176
+ end
177
+ end
178
+
179
+ def parentheses_needed?(node)
180
+ !parentheses_not_needed?(node)
181
+ end
182
+
183
+ def parentheses_not_needed?(node)
184
+ node.variable? ||
185
+ node.literal? ||
186
+ node.reference? ||
187
+ node.const_type? ||
188
+ node.begin_type? ||
189
+ parenthesized_call_node?(node)
190
+ end
191
+
192
+ def parenthesized_call_node?(node)
193
+ node.call_type? && (node.arguments.empty? || node.parenthesized_call?)
194
+ end
195
+ end
196
+ end
197
+ end
198
+ end
@@ -138,3 +138,4 @@ require_relative 'rails/where_exists'
138
138
  require_relative 'rails/where_missing'
139
139
  require_relative 'rails/where_not'
140
140
  require_relative 'rails/where_not_with_multiple_conditions'
141
+ require_relative 'rails/where_range'
@@ -178,7 +178,7 @@ module RuboCop
178
178
  attr_reader :table_name
179
179
 
180
180
  def initialize(node)
181
- super(node)
181
+ super
182
182
 
183
183
  @table_name = node.first_argument.value
184
184
  @columns, @expression = build_columns_or_expr(node.arguments[1])
@@ -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.24.1'
7
+ STRING = '2.25.1'
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.24.1
4
+ version: 2.25.1
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: 2024-03-25 00:00:00.000000000 Z
13
+ date: 2024-06-29 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: activesupport
@@ -232,6 +232,7 @@ files:
232
232
  - lib/rubocop/cop/rails/where_missing.rb
233
233
  - lib/rubocop/cop/rails/where_not.rb
234
234
  - lib/rubocop/cop/rails/where_not_with_multiple_conditions.rb
235
+ - lib/rubocop/cop/rails/where_range.rb
235
236
  - lib/rubocop/cop/rails_cops.rb
236
237
  - lib/rubocop/rails.rb
237
238
  - lib/rubocop/rails/inject.rb
@@ -245,7 +246,7 @@ metadata:
245
246
  homepage_uri: https://docs.rubocop.org/rubocop-rails/
246
247
  changelog_uri: https://github.com/rubocop/rubocop-rails/blob/master/CHANGELOG.md
247
248
  source_code_uri: https://github.com/rubocop/rubocop-rails/
248
- documentation_uri: https://docs.rubocop.org/rubocop-rails/2.24/
249
+ documentation_uri: https://docs.rubocop.org/rubocop-rails/2.25/
249
250
  bug_tracker_uri: https://github.com/rubocop/rubocop-rails/issues
250
251
  rubygems_mfa_required: 'true'
251
252
  post_install_message:
@@ -263,7 +264,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
263
264
  - !ruby/object:Gem::Version
264
265
  version: '0'
265
266
  requirements: []
266
- rubygems_version: 3.3.26
267
+ rubygems_version: 3.5.11
267
268
  signing_key:
268
269
  specification_version: 4
269
270
  summary: Automatic Rails code style checking tool.