rubocop-migration 0.2.0 → 0.3.2
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 +5 -5
- data/.rubocop.yml +58 -0
- data/CHANGELOG.md +5 -0
- data/CODE_OF_CONDUCT.md +84 -0
- data/Gemfile +11 -1
- data/Gemfile.lock +89 -0
- data/LICENSE.txt +21 -0
- data/README.md +43 -18
- data/Rakefile +9 -3
- data/config/default.yml +151 -0
- data/data/reserved_words_mysql.txt +750 -0
- data/lib/rubocop/cop/migration/add_check_constraint.rb +111 -0
- data/lib/rubocop/cop/migration/add_column_with_default_value.rb +229 -0
- data/lib/rubocop/cop/migration/add_foreign_key.rb +166 -0
- data/lib/rubocop/cop/migration/add_index_columns_count.rb +114 -0
- data/lib/rubocop/cop/migration/add_index_concurrently.rb +164 -0
- data/lib/rubocop/cop/migration/add_index_duplicate.rb +183 -0
- data/lib/rubocop/cop/migration/batch_in_batches.rb +95 -0
- data/lib/rubocop/cop/migration/batch_in_transaction.rb +83 -0
- data/lib/rubocop/cop/migration/batch_with_throttling.rb +108 -0
- data/lib/rubocop/cop/migration/change_column.rb +113 -0
- data/lib/rubocop/cop/migration/change_column_null.rb +128 -0
- data/lib/rubocop/cop/migration/create_table_force.rb +89 -0
- data/lib/rubocop/cop/migration/jsonb.rb +131 -0
- data/lib/rubocop/cop/migration/remove_column.rb +246 -0
- data/lib/rubocop/cop/migration/rename_column.rb +81 -0
- data/lib/rubocop/cop/migration/rename_table.rb +79 -0
- data/lib/rubocop/cop/migration/reserved_word_mysql.rb +232 -0
- data/lib/rubocop/migration/config_loader.rb +51 -0
- data/lib/rubocop/migration/cop_concerns/batch_processing.rb +34 -0
- data/lib/rubocop/migration/cop_concerns/column_type_method.rb +28 -0
- data/lib/rubocop/migration/cop_concerns/disable_ddl_transaction.rb +51 -0
- data/lib/rubocop/migration/cop_concerns.rb +11 -0
- data/lib/rubocop/migration/rubocop_extension.rb +15 -0
- data/lib/rubocop/migration/version.rb +4 -2
- data/lib/rubocop/migration.rb +23 -0
- data/rubocop-migration.gemspec +25 -31
- metadata +50 -137
- data/.gitignore +0 -15
- data/bin/console +0 -14
- data/bin/setup +0 -8
- data/circle.yml +0 -6
- data/lib/rubocop/cop/migration/unsafe_migration.rb +0 -69
- data/lib/rubocop/migration/strong_migrations_checker.rb +0 -47
- data/lib/rubocop-migration.rb +0 -16
- data/vendor/.gitkeep +0 -0
@@ -0,0 +1,111 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Migration
|
6
|
+
# Activate a check constraint in a separate migration in PostgreSQL.
|
7
|
+
#
|
8
|
+
# Adding a check constraint without `NOT VALID` blocks reads and writes in Postgres and
|
9
|
+
# blocks writes in MySQL and MariaDB while every row is checked.
|
10
|
+
#
|
11
|
+
# @safety
|
12
|
+
# Only meaningful in PostgreSQL.
|
13
|
+
#
|
14
|
+
# @example
|
15
|
+
# # bad
|
16
|
+
# class AddCheckConstraintToOrdersPrice < ActiveRecord::Migration[7.0]
|
17
|
+
# def change
|
18
|
+
# add_check_constraint :orders, 'price > 0', name: 'orders_price_positive'
|
19
|
+
# end
|
20
|
+
# end
|
21
|
+
#
|
22
|
+
# # good
|
23
|
+
# class AddCheckConstraintToOrdersPriceWithoutValidation < ActiveRecord::Migration[7.0]
|
24
|
+
# def change
|
25
|
+
# add_check_constraint :orders, 'price > 0', name: 'orders_price_positive', validate: false
|
26
|
+
# end
|
27
|
+
# end
|
28
|
+
#
|
29
|
+
# class ActivateCheckConstraintOnOrdersPrice < ActiveRecord::Migration[7.0]
|
30
|
+
# def change
|
31
|
+
# validate_check_constraint :orders, name: 'orders_price_positive'
|
32
|
+
# end
|
33
|
+
# end
|
34
|
+
class AddCheckConstraint < RuboCop::Cop::Base
|
35
|
+
extend AutoCorrector
|
36
|
+
|
37
|
+
MSG = 'Activate a check constraint in a separate migration in PostgreSQL.'
|
38
|
+
|
39
|
+
RESTRICT_ON_SEND = %i[
|
40
|
+
add_check_constraint
|
41
|
+
].freeze
|
42
|
+
|
43
|
+
# @param node [RuboCop::AST::SendNode]
|
44
|
+
# @return [void]
|
45
|
+
def on_send(node)
|
46
|
+
return unless bad?(node)
|
47
|
+
|
48
|
+
add_offense(node) do |corrector|
|
49
|
+
autocorrect(corrector, node)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
# @!method add_check_constraint?(node)
|
56
|
+
# @param node [RuboCop::AST::SendNode]
|
57
|
+
# @return [Boolean]
|
58
|
+
def_node_matcher :add_check_constraint?, <<~PATTERN
|
59
|
+
(send
|
60
|
+
nil?
|
61
|
+
:add_check_constraint
|
62
|
+
...
|
63
|
+
)
|
64
|
+
PATTERN
|
65
|
+
|
66
|
+
# @!method add_check_constraint_with_validate_false?(node)
|
67
|
+
# @param node [RuboCop::AST::SendNode]
|
68
|
+
# @return [Boolean]
|
69
|
+
def_node_matcher :add_check_constraint_with_validate_false?, <<~PATTERN
|
70
|
+
(send
|
71
|
+
nil?
|
72
|
+
:add_check_constraint
|
73
|
+
_
|
74
|
+
_
|
75
|
+
(hash
|
76
|
+
<
|
77
|
+
(pair
|
78
|
+
(sym :validate)
|
79
|
+
false
|
80
|
+
)
|
81
|
+
...
|
82
|
+
>
|
83
|
+
)
|
84
|
+
)
|
85
|
+
PATTERN
|
86
|
+
|
87
|
+
# @param corrector [RuboCop::Cop::Corrector]
|
88
|
+
# @param node [RuboCop::AST::SendNode]
|
89
|
+
# @return [void]
|
90
|
+
def autocorrect(
|
91
|
+
corrector,
|
92
|
+
node
|
93
|
+
)
|
94
|
+
target = node.last_argument
|
95
|
+
target = target.pairs.last if target.hash_type?
|
96
|
+
corrector.insert_after(
|
97
|
+
target,
|
98
|
+
', validate: false'
|
99
|
+
)
|
100
|
+
end
|
101
|
+
|
102
|
+
# @param node [RuboCop::AST::SendNode]
|
103
|
+
# @return [Boolean]
|
104
|
+
def bad?(node)
|
105
|
+
add_check_constraint?(node) &&
|
106
|
+
!add_check_constraint_with_validate_false?(node)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
@@ -0,0 +1,229 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Migration
|
6
|
+
# Add the column without a default value then change the default.
|
7
|
+
#
|
8
|
+
# In earlier versions of Postgres, MySQL, and MariaDB,
|
9
|
+
# adding a column with a default value to an existing table causes the entire table to be rewritten.
|
10
|
+
# During this time, reads and writes are blocked in Postgres, and writes are blocked in MySQL and MariaDB.
|
11
|
+
#
|
12
|
+
# @safety
|
13
|
+
# Only meaningful in earlier versions of Postgres, MySQL, and MariaDB.
|
14
|
+
#
|
15
|
+
# @example
|
16
|
+
# # bad
|
17
|
+
# class AddSomeColumnToUsers < ActiveRecord::Migration[7.0]
|
18
|
+
# def change
|
19
|
+
# add_column :users, :some_column, :string, default: 'some value'
|
20
|
+
# end
|
21
|
+
# end
|
22
|
+
#
|
23
|
+
# # good
|
24
|
+
# class AddSomeColumnToUsers < ActiveRecord::Migration[7.0]
|
25
|
+
# def change
|
26
|
+
# add_column :users, :some_column, :string
|
27
|
+
# change_column_default :users, :some_column, 'some value'
|
28
|
+
# end
|
29
|
+
# end
|
30
|
+
class AddColumnWithDefaultValue < RuboCop::Cop::Base
|
31
|
+
extend AutoCorrector
|
32
|
+
|
33
|
+
include RangeHelp
|
34
|
+
include ::RuboCop::Migration::CopConcerns::ColumnTypeMethod
|
35
|
+
|
36
|
+
MSG = 'Add the column without a default value then change the default.'
|
37
|
+
|
38
|
+
RESTRICT_ON_SEND = [
|
39
|
+
:add_column,
|
40
|
+
*COLUMN_TYPE_METHOD_NAMES
|
41
|
+
].freeze
|
42
|
+
|
43
|
+
# @param node [RuboCop::AST::SendNode]
|
44
|
+
# @return [void]
|
45
|
+
def on_send(node)
|
46
|
+
return unless target_method?(node)
|
47
|
+
|
48
|
+
default_option_node = non_nil_default_option_node_from(node)
|
49
|
+
return unless default_option_node
|
50
|
+
|
51
|
+
add_offense(default_option_node) do |corrector|
|
52
|
+
autocorrect(
|
53
|
+
corrector,
|
54
|
+
default_option_node: default_option_node,
|
55
|
+
send_node: node
|
56
|
+
)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
# @!method add_column?(node)
|
63
|
+
# @param node [RuboCop::AST::SendNode]
|
64
|
+
# @return [Boolean]
|
65
|
+
def_node_matcher :add_column?, <<~PATTERN
|
66
|
+
(send
|
67
|
+
nil?
|
68
|
+
:add_column
|
69
|
+
...
|
70
|
+
)
|
71
|
+
PATTERN
|
72
|
+
|
73
|
+
# @!method column_type_method?(node)
|
74
|
+
# @param node [RuboCop::AST::SendNode]
|
75
|
+
# @return [Boolean]
|
76
|
+
def_node_matcher :column_type_method?, <<~PATTERN
|
77
|
+
(send
|
78
|
+
lvar
|
79
|
+
COLUMN_TYPE_METHOD_NAMES
|
80
|
+
...
|
81
|
+
)
|
82
|
+
PATTERN
|
83
|
+
|
84
|
+
# @!method non_nil_default_option_node_from(node)
|
85
|
+
# @param node [RuboCop::AST::SendNode]
|
86
|
+
# @return [RuboCop::AST::PairNode, nil]
|
87
|
+
def_node_matcher :non_nil_default_option_node_from, <<~PATTERN
|
88
|
+
(send
|
89
|
+
_
|
90
|
+
_
|
91
|
+
...
|
92
|
+
(hash
|
93
|
+
<
|
94
|
+
$(pair
|
95
|
+
(sym :default)
|
96
|
+
!nil
|
97
|
+
)
|
98
|
+
>
|
99
|
+
...
|
100
|
+
)
|
101
|
+
)
|
102
|
+
PATTERN
|
103
|
+
|
104
|
+
# @param corrector [RuboCop::Cop::Corrector]
|
105
|
+
# @param default_option_node [RuboCop::AST::PairNode]
|
106
|
+
# @param send_node [RuboCop::AST::SendNode]
|
107
|
+
# @return [void]
|
108
|
+
def autocorrect(
|
109
|
+
corrector,
|
110
|
+
default_option_node:,
|
111
|
+
send_node:
|
112
|
+
)
|
113
|
+
remove_pair(
|
114
|
+
corrector,
|
115
|
+
default_option_node
|
116
|
+
)
|
117
|
+
insert_change_column_default(
|
118
|
+
corrector,
|
119
|
+
default_option_node: default_option_node,
|
120
|
+
send_node: send_node
|
121
|
+
)
|
122
|
+
end
|
123
|
+
|
124
|
+
# @param node [RuboCop::AST::SendNode]
|
125
|
+
# @return [RuboCop::AST::SymNode]
|
126
|
+
def find_column_node_from(node)
|
127
|
+
case node.method_name
|
128
|
+
when :add_column
|
129
|
+
node.arguments[1]
|
130
|
+
else
|
131
|
+
node.first_argument
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
# @param node [RuboCop::AST::SendNode]
|
136
|
+
# @return [RuboCop::AST::SendNode]
|
137
|
+
def find_insertion_target_node_from(node)
|
138
|
+
case node.method_name
|
139
|
+
when :add_column
|
140
|
+
node
|
141
|
+
else
|
142
|
+
node.each_ancestor(:block).first
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
# @param node [RuboCop::AST::SendNode]
|
147
|
+
# @return [RuboCop::AST::SymNode]
|
148
|
+
def find_table_node_from(node)
|
149
|
+
case node.method_name
|
150
|
+
when :add_column
|
151
|
+
node.first_argument
|
152
|
+
else
|
153
|
+
node.each_ancestor(:block).first.send_node.first_argument
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
# @param node [RuboCop::AST::SendNode]
|
158
|
+
# @return [Boolean]
|
159
|
+
def in_change_table?(node)
|
160
|
+
node.each_ancestor(:block).first&.method?(:change_table)
|
161
|
+
end
|
162
|
+
|
163
|
+
# @param corrector [RuboCop::Cop::Corrector]
|
164
|
+
# @param node [RuboCop::AST::Node]
|
165
|
+
# @param string [String]
|
166
|
+
def insert_after_with_same_indentation(
|
167
|
+
corrector,
|
168
|
+
node,
|
169
|
+
string
|
170
|
+
)
|
171
|
+
corrector.insert_after(
|
172
|
+
node,
|
173
|
+
format(
|
174
|
+
"\n%<indentation>s%<string>s",
|
175
|
+
indentation: ' ' * node.location.column,
|
176
|
+
string: string
|
177
|
+
)
|
178
|
+
)
|
179
|
+
end
|
180
|
+
|
181
|
+
# @param corrector [RuboCop::Cop::Corrector]
|
182
|
+
# @param default_option_node [RuboCop::AST::PairNode]
|
183
|
+
# @param send_node [RuboCop::AST::SendNode]
|
184
|
+
# @return [void]
|
185
|
+
def insert_change_column_default(
|
186
|
+
corrector,
|
187
|
+
default_option_node:,
|
188
|
+
send_node:
|
189
|
+
)
|
190
|
+
insert_after_with_same_indentation(
|
191
|
+
corrector,
|
192
|
+
find_insertion_target_node_from(send_node),
|
193
|
+
format(
|
194
|
+
'change_column_default %<table>s, %<column>s, %<default>s',
|
195
|
+
column: find_column_node_from(send_node).source,
|
196
|
+
default: default_option_node.value.source,
|
197
|
+
table: find_table_node_from(send_node).source
|
198
|
+
)
|
199
|
+
)
|
200
|
+
end
|
201
|
+
|
202
|
+
# @param corrector [RuboCop::Cop::Corrector]
|
203
|
+
# @param node [RuboCop::AST::Node]
|
204
|
+
# @return [void]
|
205
|
+
def remove_pair(
|
206
|
+
corrector,
|
207
|
+
node
|
208
|
+
)
|
209
|
+
corrector.remove(
|
210
|
+
range_with_surrounding_comma(
|
211
|
+
range_with_surrounding_space(
|
212
|
+
node.location.expression,
|
213
|
+
side: :left
|
214
|
+
),
|
215
|
+
:left
|
216
|
+
)
|
217
|
+
)
|
218
|
+
end
|
219
|
+
|
220
|
+
# @param node [RuboCop::AST::SendNode]
|
221
|
+
# @return [Boolean]
|
222
|
+
def target_method?(node)
|
223
|
+
add_column?(node) ||
|
224
|
+
(column_type_method?(node) && in_change_table?(node))
|
225
|
+
end
|
226
|
+
end
|
227
|
+
end
|
228
|
+
end
|
229
|
+
end
|
@@ -0,0 +1,166 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Migration
|
6
|
+
# Activate foreign key validation in a separate migration in PostgreSQL.
|
7
|
+
#
|
8
|
+
# To avoid blocking writes on both tables.
|
9
|
+
#
|
10
|
+
# @safety
|
11
|
+
# Only meaningful in PostgreSQL.
|
12
|
+
#
|
13
|
+
# @example
|
14
|
+
# # bad
|
15
|
+
# class AddForeignKeyFromArticlesToUsers < ActiveRecord::Migration[7.0]
|
16
|
+
# def change
|
17
|
+
# add_foreign_key :articles, :users
|
18
|
+
# end
|
19
|
+
# end
|
20
|
+
#
|
21
|
+
# # good
|
22
|
+
# class AddForeignKeyFromArticlesToUsersWithoutValidation < ActiveRecord::Migration[7.0]
|
23
|
+
# def change
|
24
|
+
# add_foreign_key :articles, :users, validate: false
|
25
|
+
# end
|
26
|
+
# end
|
27
|
+
#
|
28
|
+
# class ActivateForeignKeyValidationFromArticlesToUsers < ActiveRecord::Migration[7.0]
|
29
|
+
# def change
|
30
|
+
# validate_foreign_key :articles, :users
|
31
|
+
# end
|
32
|
+
# end
|
33
|
+
class AddForeignKey < RuboCop::Cop::Base
|
34
|
+
extend AutoCorrector
|
35
|
+
|
36
|
+
include RangeHelp
|
37
|
+
|
38
|
+
MSG = 'Activate foreign key validation in a separate migration in PostgreSQL.'
|
39
|
+
|
40
|
+
RESTRICT_ON_SEND = %i[
|
41
|
+
add_foreign_key
|
42
|
+
add_reference
|
43
|
+
].freeze
|
44
|
+
|
45
|
+
# @param node [RuboCop::AST::SendNode]
|
46
|
+
# @return [void]
|
47
|
+
def on_send(node)
|
48
|
+
return unless bad?(node)
|
49
|
+
|
50
|
+
add_offense(node) do |corrector|
|
51
|
+
autocorrect(corrector, node)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
# @!method add_foreign_key_without_validate_option?(node)
|
58
|
+
# @param node [RuboCop::AST::SendNode]
|
59
|
+
# @return [Boolean]
|
60
|
+
def_node_matcher :add_foreign_key_without_validate_option?, <<~PATTERN
|
61
|
+
(send
|
62
|
+
nil?
|
63
|
+
:add_foreign_key
|
64
|
+
_
|
65
|
+
_
|
66
|
+
(hash
|
67
|
+
(pair
|
68
|
+
!(sym :validate)
|
69
|
+
_
|
70
|
+
)*
|
71
|
+
)?
|
72
|
+
)
|
73
|
+
PATTERN
|
74
|
+
|
75
|
+
# @!method option_validate_true_value_node_from_add_foreign_key(node)
|
76
|
+
# @param node [RuboCop::AST::SendNode]
|
77
|
+
# @return [RuboCop::AST::PairNode]
|
78
|
+
def_node_matcher :option_validate_true_value_node_from_add_foreign_key, <<~PATTERN
|
79
|
+
(send
|
80
|
+
nil?
|
81
|
+
:add_foreign_key
|
82
|
+
_
|
83
|
+
_
|
84
|
+
(hash
|
85
|
+
<
|
86
|
+
(pair
|
87
|
+
(sym :validate)
|
88
|
+
$true
|
89
|
+
)
|
90
|
+
...
|
91
|
+
>
|
92
|
+
)
|
93
|
+
)
|
94
|
+
PATTERN
|
95
|
+
|
96
|
+
# @!method option_foreign_key_true_node_from_add_reference(node)
|
97
|
+
# @param node [RuboCop::AST::SendNode]
|
98
|
+
# @return [RuboCop::AST::PairNode]
|
99
|
+
def_node_matcher :option_foreign_key_true_node_from_add_reference, <<~PATTERN
|
100
|
+
(send
|
101
|
+
nil?
|
102
|
+
:add_reference
|
103
|
+
_
|
104
|
+
_
|
105
|
+
(hash
|
106
|
+
<
|
107
|
+
$(pair
|
108
|
+
(sym :foreign_key)
|
109
|
+
true
|
110
|
+
)
|
111
|
+
...
|
112
|
+
>
|
113
|
+
)
|
114
|
+
)
|
115
|
+
PATTERN
|
116
|
+
|
117
|
+
# @param node [RuboCop::AST::SendNode]
|
118
|
+
# @return [Boolean]
|
119
|
+
def add_foreign_key_with_validate_option_true?(node)
|
120
|
+
option_validate_true_value_node_from_add_foreign_key(node)
|
121
|
+
end
|
122
|
+
|
123
|
+
# @param node [RuboCop::AST::SendNode]
|
124
|
+
# @return [Boolean]
|
125
|
+
def add_reference_with_validate_option_true?(node)
|
126
|
+
option_foreign_key_true_node_from_add_reference(node)
|
127
|
+
end
|
128
|
+
|
129
|
+
# @param corrector [RuboCop::Cop::Corrector]
|
130
|
+
# @param node [RuboCop::AST::SendNode]
|
131
|
+
# @return [void]
|
132
|
+
def autocorrect(
|
133
|
+
corrector,
|
134
|
+
node
|
135
|
+
)
|
136
|
+
if add_foreign_key_without_validate_option?(node)
|
137
|
+
corrector.insert_after(node.last_argument, ', validate: false')
|
138
|
+
elsif add_foreign_key_with_validate_option_true?(node)
|
139
|
+
corrector.replace(
|
140
|
+
option_validate_true_value_node_from_add_foreign_key(node),
|
141
|
+
'false'
|
142
|
+
)
|
143
|
+
elsif add_reference_with_validate_option_true?(node)
|
144
|
+
corrector.remove(
|
145
|
+
range_with_surrounding_comma(
|
146
|
+
range_with_surrounding_space(
|
147
|
+
option_foreign_key_true_node_from_add_reference(node).location.expression,
|
148
|
+
side: :left
|
149
|
+
),
|
150
|
+
:left
|
151
|
+
)
|
152
|
+
)
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
# @param node [RuboCop::AST::SendNode]
|
157
|
+
# @return [Boolean]
|
158
|
+
def bad?(node)
|
159
|
+
add_foreign_key_without_validate_option?(node) ||
|
160
|
+
add_foreign_key_with_validate_option_true?(node) ||
|
161
|
+
add_reference_with_validate_option_true?(node)
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
@@ -0,0 +1,114 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Migration
|
6
|
+
# Keep non-unique index columns count less than a specified number.
|
7
|
+
#
|
8
|
+
# Adding a non-unique index with more than three columns rarely improves performance.
|
9
|
+
# Instead, start an index with columns that narrow down the results the most.
|
10
|
+
#
|
11
|
+
# @example
|
12
|
+
# # bad
|
13
|
+
# add_index :users, %i[a b c d]
|
14
|
+
#
|
15
|
+
# # good (`MaxColumnsCount: 3` by default)
|
16
|
+
# add_index :users, %i[a b c]
|
17
|
+
class AddIndexColumnsCount < RuboCop::Cop::Base
|
18
|
+
RESTRICT_ON_SEND = %i[
|
19
|
+
add_index
|
20
|
+
index
|
21
|
+
].freeze
|
22
|
+
|
23
|
+
# @param node [RuboCop::AST::SendNode]
|
24
|
+
# @return [void]
|
25
|
+
def on_send(node)
|
26
|
+
return if with_unique_option?(node)
|
27
|
+
|
28
|
+
column_names_node = column_names_node_from(node)
|
29
|
+
return unless column_names_node
|
30
|
+
|
31
|
+
column_names_count = columns_count_from(column_names_node)
|
32
|
+
return if column_names_count <= max_columns_count
|
33
|
+
|
34
|
+
add_offense(
|
35
|
+
column_names_node,
|
36
|
+
message: "Keep unique index columns count less than #{max_columns_count}."
|
37
|
+
)
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
# @!method column_names_node_from_add_index(node)
|
43
|
+
# @param node [RuboCop::AST::SendNode]
|
44
|
+
# @return [RuboCop::AST::Node, nil]
|
45
|
+
def_node_matcher :column_names_node_from_add_index, <<~PATTERN
|
46
|
+
(send
|
47
|
+
nil?
|
48
|
+
:add_index
|
49
|
+
_
|
50
|
+
$({array | str | sym} ...)
|
51
|
+
...
|
52
|
+
)
|
53
|
+
PATTERN
|
54
|
+
|
55
|
+
# @!method column_names_node_from_index(node)
|
56
|
+
# @param node [RuboCop::AST::SendNode]
|
57
|
+
# @return [RuboCop::AST::Node, nil]
|
58
|
+
def_node_matcher :column_names_node_from_index, <<~PATTERN
|
59
|
+
(send
|
60
|
+
lvar
|
61
|
+
:index
|
62
|
+
$({array | str | sym} ...)
|
63
|
+
...
|
64
|
+
)
|
65
|
+
PATTERN
|
66
|
+
|
67
|
+
# @!method with_unique_option?(node)
|
68
|
+
# @param node [RuboCop::AST::SendNode]
|
69
|
+
# @return [Boolean]
|
70
|
+
def_node_matcher :with_unique_option?, <<~PATTERN
|
71
|
+
(send
|
72
|
+
...
|
73
|
+
(hash
|
74
|
+
<
|
75
|
+
(pair
|
76
|
+
(sym :unique)
|
77
|
+
true
|
78
|
+
)
|
79
|
+
...
|
80
|
+
>
|
81
|
+
)
|
82
|
+
)
|
83
|
+
PATTERN
|
84
|
+
|
85
|
+
# @param node [RuboCop::AST::SendNode]
|
86
|
+
# @return [RuboCop::AST::Node, nil]
|
87
|
+
def column_names_node_from(node)
|
88
|
+
case node.method_name
|
89
|
+
when :add_index
|
90
|
+
column_names_node_from_add_index(node)
|
91
|
+
when :index
|
92
|
+
column_names_node_from_index(node)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
# @param node [RuboCop::AST::Node]
|
97
|
+
# @return [Integer, nil]
|
98
|
+
def columns_count_from(node)
|
99
|
+
case node.type
|
100
|
+
when :array
|
101
|
+
node.values.count
|
102
|
+
when :sym
|
103
|
+
1
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
# @return [Integer]
|
108
|
+
def max_columns_count
|
109
|
+
cop_config['MaxColumnsCount']
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|