rubocop-rails 2.24.1 → 2.25.1
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.
- checksums.yaml +4 -4
- data/README.md +1 -1
- data/config/default.yml +12 -3
- data/lib/rubocop/cop/mixin/target_rails_version.rb +29 -2
- data/lib/rubocop/cop/rails/bulk_change_table.rb +8 -2
- data/lib/rubocop/cop/rails/http_status.rb +12 -2
- data/lib/rubocop/cop/rails/link_to_blank.rb +2 -2
- data/lib/rubocop/cop/rails/not_null_column.rb +93 -13
- data/lib/rubocop/cop/rails/pick.rb +4 -0
- data/lib/rubocop/cop/rails/skips_model_validations.rb +4 -1
- data/lib/rubocop/cop/rails/unknown_env.rb +1 -1
- data/lib/rubocop/cop/rails/unused_ignored_columns.rb +6 -0
- data/lib/rubocop/cop/rails/validation.rb +7 -2
- data/lib/rubocop/cop/rails/where_range.rb +198 -0
- data/lib/rubocop/cop/rails_cops.rb +1 -0
- data/lib/rubocop/rails/schema_loader/schema.rb +1 -1
- data/lib/rubocop/rails/version.rb +1 -1
- metadata +5 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b55703b258e4df9bae3a9a8c1b77a4fa143af6af3ed9b1b22118fd839d1ce06c
|
4
|
+
data.tar.gz: 24568d7d8d22d69469ae9a4aaf426ea45bf848989604a8744ab6493bc0b222a3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
[](https://badge.fury.io/rb/rubocop-rails)
|
4
|
-
[](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.
|
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:
|
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
|
-
|
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
|
-
|
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
|
6
|
+
# Checks for add_column calls with a NOT NULL constraint without a default
|
7
|
+
# value.
|
7
8
|
#
|
8
|
-
#
|
9
|
-
#
|
10
|
-
#
|
11
|
-
#
|
12
|
-
#
|
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
|
-
#
|
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
|
-
|
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
|
-
(
|
69
|
+
(call _ {:insert :insert!} _ {
|
67
70
|
!(hash ...)
|
68
71
|
(hash <(pair (sym !{:returning :unique_by}) _) ...>)
|
69
72
|
} ...)
|
@@ -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,
|
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
|
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.
|
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-
|
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.
|
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.
|
267
|
+
rubygems_version: 3.5.11
|
267
268
|
signing_key:
|
268
269
|
specification_version: 4
|
269
270
|
summary: Automatic Rails code style checking tool.
|