rubocop-rails 2.4.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 +7 -0
- data/LICENSE.txt +20 -0
- data/README.md +92 -0
- data/bin/setup +7 -0
- data/config/default.yml +510 -0
- data/lib/rubocop-rails.rb +12 -0
- data/lib/rubocop/cop/mixin/target_rails_version.rb +16 -0
- data/lib/rubocop/cop/rails/action_filter.rb +111 -0
- data/lib/rubocop/cop/rails/active_record_aliases.rb +48 -0
- data/lib/rubocop/cop/rails/active_record_override.rb +82 -0
- data/lib/rubocop/cop/rails/active_support_aliases.rb +69 -0
- data/lib/rubocop/cop/rails/application_controller.rb +36 -0
- data/lib/rubocop/cop/rails/application_job.rb +40 -0
- data/lib/rubocop/cop/rails/application_mailer.rb +40 -0
- data/lib/rubocop/cop/rails/application_record.rb +40 -0
- data/lib/rubocop/cop/rails/assert_not.rb +44 -0
- data/lib/rubocop/cop/rails/belongs_to.rb +102 -0
- data/lib/rubocop/cop/rails/blank.rb +164 -0
- data/lib/rubocop/cop/rails/bulk_change_table.rb +293 -0
- data/lib/rubocop/cop/rails/create_table_with_timestamps.rb +91 -0
- data/lib/rubocop/cop/rails/date.rb +161 -0
- data/lib/rubocop/cop/rails/delegate.rb +132 -0
- data/lib/rubocop/cop/rails/delegate_allow_blank.rb +37 -0
- data/lib/rubocop/cop/rails/dynamic_find_by.rb +91 -0
- data/lib/rubocop/cop/rails/enum_hash.rb +75 -0
- data/lib/rubocop/cop/rails/enum_uniqueness.rb +65 -0
- data/lib/rubocop/cop/rails/environment_comparison.rb +68 -0
- data/lib/rubocop/cop/rails/exit.rb +67 -0
- data/lib/rubocop/cop/rails/file_path.rb +108 -0
- data/lib/rubocop/cop/rails/find_by.rb +55 -0
- data/lib/rubocop/cop/rails/find_each.rb +51 -0
- data/lib/rubocop/cop/rails/has_and_belongs_to_many.rb +25 -0
- data/lib/rubocop/cop/rails/has_many_or_has_one_dependent.rb +106 -0
- data/lib/rubocop/cop/rails/helper_instance_variable.rb +39 -0
- data/lib/rubocop/cop/rails/http_positional_arguments.rb +117 -0
- data/lib/rubocop/cop/rails/http_status.rb +160 -0
- data/lib/rubocop/cop/rails/ignored_skip_action_filter_option.rb +94 -0
- data/lib/rubocop/cop/rails/inverse_of.rb +246 -0
- data/lib/rubocop/cop/rails/lexically_scoped_action_filter.rb +175 -0
- data/lib/rubocop/cop/rails/link_to_blank.rb +98 -0
- data/lib/rubocop/cop/rails/not_null_column.rb +67 -0
- data/lib/rubocop/cop/rails/output.rb +49 -0
- data/lib/rubocop/cop/rails/output_safety.rb +99 -0
- data/lib/rubocop/cop/rails/pluralization_grammar.rb +107 -0
- data/lib/rubocop/cop/rails/presence.rb +148 -0
- data/lib/rubocop/cop/rails/present.rb +153 -0
- data/lib/rubocop/cop/rails/rake_environment.rb +91 -0
- data/lib/rubocop/cop/rails/read_write_attribute.rb +74 -0
- data/lib/rubocop/cop/rails/redundant_allow_nil.rb +111 -0
- data/lib/rubocop/cop/rails/redundant_receiver_in_with_options.rb +136 -0
- data/lib/rubocop/cop/rails/reflection_class_name.rb +37 -0
- data/lib/rubocop/cop/rails/refute_methods.rb +76 -0
- data/lib/rubocop/cop/rails/relative_date_constant.rb +102 -0
- data/lib/rubocop/cop/rails/request_referer.rb +56 -0
- data/lib/rubocop/cop/rails/reversible_migration.rb +284 -0
- data/lib/rubocop/cop/rails/safe_navigation.rb +85 -0
- data/lib/rubocop/cop/rails/safe_navigation_with_blank.rb +48 -0
- data/lib/rubocop/cop/rails/save_bang.rb +331 -0
- data/lib/rubocop/cop/rails/scope_args.rb +29 -0
- data/lib/rubocop/cop/rails/skips_model_validations.rb +87 -0
- data/lib/rubocop/cop/rails/time_zone.rb +249 -0
- data/lib/rubocop/cop/rails/uniq_before_pluck.rb +105 -0
- data/lib/rubocop/cop/rails/unknown_env.rb +84 -0
- data/lib/rubocop/cop/rails/validation.rb +147 -0
- data/lib/rubocop/cop/rails_cops.rb +61 -0
- data/lib/rubocop/rails.rb +12 -0
- data/lib/rubocop/rails/inject.rb +18 -0
- data/lib/rubocop/rails/version.rb +10 -0
- metadata +148 -0
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Rails
|
6
|
+
# This cop checks if the value of the option `class_name`, in
|
7
|
+
# the definition of a reflection is a string.
|
8
|
+
#
|
9
|
+
# @example
|
10
|
+
# # bad
|
11
|
+
# has_many :accounts, class_name: Account
|
12
|
+
# has_many :accounts, class_name: Account.name
|
13
|
+
#
|
14
|
+
# # good
|
15
|
+
# has_many :accounts, class_name: 'Account'
|
16
|
+
class ReflectionClassName < Cop
|
17
|
+
MSG = 'Use a string value for `class_name`.'
|
18
|
+
|
19
|
+
def_node_matcher :association_with_reflection, <<~PATTERN
|
20
|
+
(send nil? {:has_many :has_one :belongs_to} _
|
21
|
+
(hash <$#reflection_class_name ...>)
|
22
|
+
)
|
23
|
+
PATTERN
|
24
|
+
|
25
|
+
def_node_matcher :reflection_class_name, <<~PATTERN
|
26
|
+
(pair (sym :class_name) [!dstr !str !sym])
|
27
|
+
PATTERN
|
28
|
+
|
29
|
+
def on_send(node)
|
30
|
+
association_with_reflection(node) do |reflection_class_name|
|
31
|
+
add_offense(node, location: reflection_class_name.loc.expression)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Rails
|
6
|
+
#
|
7
|
+
# Use `assert_not` methods instead of `refute` methods.
|
8
|
+
#
|
9
|
+
# @example
|
10
|
+
# # bad
|
11
|
+
# refute false
|
12
|
+
# refute_empty [1, 2, 3]
|
13
|
+
# refute_equal true, false
|
14
|
+
#
|
15
|
+
# # good
|
16
|
+
# assert_not false
|
17
|
+
# assert_not_empty [1, 2, 3]
|
18
|
+
# assert_not_equal true, false
|
19
|
+
#
|
20
|
+
class RefuteMethods < Cop
|
21
|
+
MSG = 'Prefer `%<assert_method>s` over `%<refute_method>s`.'
|
22
|
+
|
23
|
+
CORRECTIONS = {
|
24
|
+
refute: 'assert_not',
|
25
|
+
refute_empty: 'assert_not_empty',
|
26
|
+
refute_equal: 'assert_not_equal',
|
27
|
+
refute_in_delta: 'assert_not_in_delta',
|
28
|
+
refute_in_epsilon: 'assert_not_in_epsilon',
|
29
|
+
refute_includes: 'assert_not_includes',
|
30
|
+
refute_instance_of: 'assert_not_instance_of',
|
31
|
+
refute_kind_of: 'assert_not_kind_of',
|
32
|
+
refute_nil: 'assert_not_nil',
|
33
|
+
refute_operator: 'assert_not_operator',
|
34
|
+
refute_predicate: 'assert_not_predicate',
|
35
|
+
refute_respond_to: 'assert_not_respond_to',
|
36
|
+
refute_same: 'assert_not_same',
|
37
|
+
refute_match: 'assert_no_match'
|
38
|
+
}.freeze
|
39
|
+
|
40
|
+
OFFENSIVE_METHODS = CORRECTIONS.keys.freeze
|
41
|
+
|
42
|
+
def_node_matcher :offensive?, '(send nil? #refute_method? ...)'
|
43
|
+
|
44
|
+
def on_send(node)
|
45
|
+
return unless offensive?(node)
|
46
|
+
|
47
|
+
message = offense_message(node.method_name)
|
48
|
+
add_offense(node, location: :selector, message: message)
|
49
|
+
end
|
50
|
+
|
51
|
+
def autocorrect(node)
|
52
|
+
lambda do |corrector|
|
53
|
+
corrector.replace(
|
54
|
+
node.loc.selector,
|
55
|
+
CORRECTIONS[node.method_name]
|
56
|
+
)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
def refute_method?(method_name)
|
63
|
+
OFFENSIVE_METHODS.include?(method_name)
|
64
|
+
end
|
65
|
+
|
66
|
+
def offense_message(method_name)
|
67
|
+
format(
|
68
|
+
MSG,
|
69
|
+
refute_method: method_name,
|
70
|
+
assert_method: CORRECTIONS[method_name]
|
71
|
+
)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Rails
|
6
|
+
# This cop checks whether constant value isn't relative date.
|
7
|
+
# Because the relative date will be evaluated only once.
|
8
|
+
#
|
9
|
+
# @example
|
10
|
+
# # bad
|
11
|
+
# class SomeClass
|
12
|
+
# EXPIRED_AT = 1.week.since
|
13
|
+
# end
|
14
|
+
#
|
15
|
+
# # good
|
16
|
+
# class SomeClass
|
17
|
+
# EXPIRES = 1.week
|
18
|
+
#
|
19
|
+
# def self.expired_at
|
20
|
+
# EXPIRES.since
|
21
|
+
# end
|
22
|
+
# end
|
23
|
+
#
|
24
|
+
# # good
|
25
|
+
# class SomeClass
|
26
|
+
# def self.expired_at
|
27
|
+
# 1.week.since
|
28
|
+
# end
|
29
|
+
# end
|
30
|
+
class RelativeDateConstant < Cop
|
31
|
+
include RangeHelp
|
32
|
+
|
33
|
+
MSG = 'Do not assign %<method_name>s to constants as it ' \
|
34
|
+
'will be evaluated only once.'
|
35
|
+
|
36
|
+
def on_casgn(node)
|
37
|
+
relative_date_assignment?(node) do |method_name|
|
38
|
+
add_offense(node, message: format(MSG, method_name: method_name))
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def on_masgn(node)
|
43
|
+
lhs, rhs = *node
|
44
|
+
|
45
|
+
return unless rhs&.array_type?
|
46
|
+
|
47
|
+
lhs.children.zip(rhs.children).each do |(name, value)|
|
48
|
+
next unless name.casgn_type?
|
49
|
+
|
50
|
+
relative_date?(value) do |method_name|
|
51
|
+
add_offense(node,
|
52
|
+
location: range_between(name.loc.expression.begin_pos,
|
53
|
+
value.loc.expression.end_pos),
|
54
|
+
message: format(MSG, method_name: method_name))
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def on_or_asgn(node)
|
60
|
+
relative_date_or_assignment?(node) do |method_name|
|
61
|
+
add_offense(node, message: format(MSG, method_name: method_name))
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def autocorrect(node)
|
66
|
+
return unless node.casgn_type?
|
67
|
+
|
68
|
+
scope, const_name, value = *node
|
69
|
+
return unless scope.nil?
|
70
|
+
|
71
|
+
indent = ' ' * node.loc.column
|
72
|
+
new_code = ["def self.#{const_name.downcase}",
|
73
|
+
"#{indent}#{value.source}",
|
74
|
+
'end'].join("\n#{indent}")
|
75
|
+
->(corrector) { corrector.replace(node.source_range, new_code) }
|
76
|
+
end
|
77
|
+
|
78
|
+
private
|
79
|
+
|
80
|
+
def_node_matcher :relative_date_assignment?, <<~PATTERN
|
81
|
+
{
|
82
|
+
(casgn _ _ (send _ ${:since :from_now :after :ago :until :before}))
|
83
|
+
(casgn _ _ ({erange irange} _ (send _ ${:since :from_now :after :ago :until :before})))
|
84
|
+
(casgn _ _ ({erange irange} (send _ ${:since :from_now :after :ago :until :before}) _))
|
85
|
+
}
|
86
|
+
PATTERN
|
87
|
+
|
88
|
+
def_node_matcher :relative_date_or_assignment?, <<~PATTERN
|
89
|
+
(:or_asgn (casgn _ _) (send _ ${:since :from_now :after :ago :until :before}))
|
90
|
+
PATTERN
|
91
|
+
|
92
|
+
def_node_matcher :relative_date?, <<~PATTERN
|
93
|
+
{
|
94
|
+
({erange irange} _ (send _ ${:since :from_now :after :ago :until :before}))
|
95
|
+
({erange irange} (send _ ${:since :from_now :after :ago :until :before}) _)
|
96
|
+
(send _ ${:since :from_now :after :ago :until :before})
|
97
|
+
}
|
98
|
+
PATTERN
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Rails
|
6
|
+
# This cop checks for consistent uses of `request.referer` or
|
7
|
+
# `request.referrer`, depending on the cop's configuration.
|
8
|
+
#
|
9
|
+
# @example EnforcedStyle: referer (default)
|
10
|
+
# # bad
|
11
|
+
# request.referrer
|
12
|
+
#
|
13
|
+
# # good
|
14
|
+
# request.referer
|
15
|
+
#
|
16
|
+
# @example EnforcedStyle: referrer
|
17
|
+
# # bad
|
18
|
+
# request.referer
|
19
|
+
#
|
20
|
+
# # good
|
21
|
+
# request.referrer
|
22
|
+
class RequestReferer < Cop
|
23
|
+
include ConfigurableEnforcedStyle
|
24
|
+
|
25
|
+
MSG = 'Use `request.%<prefer>s` instead of ' \
|
26
|
+
'`request.%<current>s`.'
|
27
|
+
|
28
|
+
def_node_matcher :referer?, <<~PATTERN
|
29
|
+
(send (send nil? :request) {:referer :referrer})
|
30
|
+
PATTERN
|
31
|
+
|
32
|
+
def on_send(node)
|
33
|
+
referer?(node) do
|
34
|
+
return unless node.method?(wrong_method_name)
|
35
|
+
|
36
|
+
add_offense(node.source_range, location: node.source_range)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def autocorrect(node)
|
41
|
+
->(corrector) { corrector.replace(node, "request.#{style}") }
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def message(_node)
|
47
|
+
format(MSG, prefer: style, current: wrong_method_name)
|
48
|
+
end
|
49
|
+
|
50
|
+
def wrong_method_name
|
51
|
+
style == :referer ? :referrer : :referer
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,284 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Rails
|
6
|
+
# This cop checks whether the change method of the migration file is
|
7
|
+
# reversible.
|
8
|
+
#
|
9
|
+
# @example
|
10
|
+
# # bad
|
11
|
+
# def change
|
12
|
+
# change_table :users do |t|
|
13
|
+
# t.remove :name
|
14
|
+
# end
|
15
|
+
# end
|
16
|
+
#
|
17
|
+
# # good
|
18
|
+
# def change
|
19
|
+
# create_table :users do |t|
|
20
|
+
# t.string :name
|
21
|
+
# end
|
22
|
+
# end
|
23
|
+
#
|
24
|
+
# # good
|
25
|
+
# def change
|
26
|
+
# reversible do |dir|
|
27
|
+
# change_table :users do |t|
|
28
|
+
# dir.up do
|
29
|
+
# t.column :name, :string
|
30
|
+
# end
|
31
|
+
#
|
32
|
+
# dir.down do
|
33
|
+
# t.remove :name
|
34
|
+
# end
|
35
|
+
# end
|
36
|
+
# end
|
37
|
+
# end
|
38
|
+
#
|
39
|
+
# @example
|
40
|
+
# # drop_table
|
41
|
+
#
|
42
|
+
# # bad
|
43
|
+
# def change
|
44
|
+
# drop_table :users
|
45
|
+
# end
|
46
|
+
#
|
47
|
+
# # good
|
48
|
+
# def change
|
49
|
+
# drop_table :users do |t|
|
50
|
+
# t.string :name
|
51
|
+
# end
|
52
|
+
# end
|
53
|
+
#
|
54
|
+
# @example
|
55
|
+
# # change_column_default
|
56
|
+
#
|
57
|
+
# # bad
|
58
|
+
# def change
|
59
|
+
# change_column_default(:suppliers, :qualification, 'new')
|
60
|
+
# end
|
61
|
+
#
|
62
|
+
# # good
|
63
|
+
# def change
|
64
|
+
# change_column_default(:posts, :state, from: nil, to: "draft")
|
65
|
+
# end
|
66
|
+
#
|
67
|
+
# @example
|
68
|
+
# # remove_column
|
69
|
+
#
|
70
|
+
# # bad
|
71
|
+
# def change
|
72
|
+
# remove_column(:suppliers, :qualification)
|
73
|
+
# end
|
74
|
+
#
|
75
|
+
# # good
|
76
|
+
# def change
|
77
|
+
# remove_column(:suppliers, :qualification, :string)
|
78
|
+
# end
|
79
|
+
#
|
80
|
+
# @example
|
81
|
+
# # remove_foreign_key
|
82
|
+
#
|
83
|
+
# # bad
|
84
|
+
# def change
|
85
|
+
# remove_foreign_key :accounts, column: :owner_id
|
86
|
+
# end
|
87
|
+
#
|
88
|
+
# # good
|
89
|
+
# def change
|
90
|
+
# remove_foreign_key :accounts, :branches
|
91
|
+
# end
|
92
|
+
#
|
93
|
+
# @example
|
94
|
+
# # change_table
|
95
|
+
#
|
96
|
+
# # bad
|
97
|
+
# def change
|
98
|
+
# change_table :users do |t|
|
99
|
+
# t.remove :name
|
100
|
+
# t.change_default :authorized, 1
|
101
|
+
# t.change :price, :string
|
102
|
+
# end
|
103
|
+
# end
|
104
|
+
#
|
105
|
+
# # good
|
106
|
+
# def change
|
107
|
+
# change_table :users do |t|
|
108
|
+
# t.string :name
|
109
|
+
# end
|
110
|
+
# end
|
111
|
+
#
|
112
|
+
# # good
|
113
|
+
# def change
|
114
|
+
# reversible do |dir|
|
115
|
+
# change_table :users do |t|
|
116
|
+
# dir.up do
|
117
|
+
# t.change :price, :string
|
118
|
+
# end
|
119
|
+
#
|
120
|
+
# dir.down do
|
121
|
+
# t.change :price, :integer
|
122
|
+
# end
|
123
|
+
# end
|
124
|
+
# end
|
125
|
+
# end
|
126
|
+
#
|
127
|
+
# @see https://api.rubyonrails.org/classes/ActiveRecord/Migration/CommandRecorder.html
|
128
|
+
class ReversibleMigration < Cop
|
129
|
+
MSG = '%<action>s is not reversible.'
|
130
|
+
|
131
|
+
def_node_matcher :irreversible_schema_statement_call, <<~PATTERN
|
132
|
+
(send nil? ${:execute :remove_belongs_to} ...)
|
133
|
+
PATTERN
|
134
|
+
|
135
|
+
def_node_matcher :drop_table_call, <<~PATTERN
|
136
|
+
(send nil? :drop_table ...)
|
137
|
+
PATTERN
|
138
|
+
|
139
|
+
def_node_matcher :remove_column_call, <<~PATTERN
|
140
|
+
(send nil? :remove_column $...)
|
141
|
+
PATTERN
|
142
|
+
|
143
|
+
def_node_matcher :remove_foreign_key_call, <<~PATTERN
|
144
|
+
(send nil? :remove_foreign_key _ $_)
|
145
|
+
PATTERN
|
146
|
+
|
147
|
+
def_node_matcher :change_table_call, <<~PATTERN
|
148
|
+
(send nil? :change_table $_ ...)
|
149
|
+
PATTERN
|
150
|
+
|
151
|
+
def on_send(node)
|
152
|
+
return unless within_change_method?(node)
|
153
|
+
return if within_reversible_or_up_only_block?(node)
|
154
|
+
|
155
|
+
check_irreversible_schema_statement_node(node)
|
156
|
+
check_drop_table_node(node)
|
157
|
+
check_reversible_hash_node(node)
|
158
|
+
check_remove_column_node(node)
|
159
|
+
check_remove_foreign_key_node(node)
|
160
|
+
end
|
161
|
+
|
162
|
+
def on_block(node)
|
163
|
+
return unless within_change_method?(node)
|
164
|
+
return if within_reversible_or_up_only_block?(node)
|
165
|
+
return if node.body.nil?
|
166
|
+
|
167
|
+
check_change_table_node(node.send_node, node.body)
|
168
|
+
end
|
169
|
+
|
170
|
+
private
|
171
|
+
|
172
|
+
def check_irreversible_schema_statement_node(node)
|
173
|
+
irreversible_schema_statement_call(node) do |method_name|
|
174
|
+
add_offense(node, message: format(MSG, action: method_name))
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
def check_drop_table_node(node)
|
179
|
+
drop_table_call(node) do
|
180
|
+
unless node.parent.block_type?
|
181
|
+
add_offense(
|
182
|
+
node,
|
183
|
+
message: format(MSG, action: 'drop_table(without block)')
|
184
|
+
)
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
def check_reversible_hash_node(node)
|
190
|
+
return if reversible_change_table_call?(node)
|
191
|
+
|
192
|
+
add_offense(
|
193
|
+
node,
|
194
|
+
message: format(
|
195
|
+
MSG, action: "#{node.method_name}(without :from and :to)"
|
196
|
+
)
|
197
|
+
)
|
198
|
+
end
|
199
|
+
|
200
|
+
def check_remove_column_node(node)
|
201
|
+
remove_column_call(node) do |args|
|
202
|
+
if args.to_a.size < 3
|
203
|
+
add_offense(
|
204
|
+
node,
|
205
|
+
message: format(MSG, action: 'remove_column(without type)')
|
206
|
+
)
|
207
|
+
end
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
def check_remove_foreign_key_node(node)
|
212
|
+
remove_foreign_key_call(node) do |arg|
|
213
|
+
if arg.hash_type?
|
214
|
+
add_offense(
|
215
|
+
node,
|
216
|
+
message: format(MSG,
|
217
|
+
action: 'remove_foreign_key(without table)')
|
218
|
+
)
|
219
|
+
end
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
def check_change_table_node(node, block)
|
224
|
+
change_table_call(node) do |arg|
|
225
|
+
if block.send_type?
|
226
|
+
check_change_table_offense(arg, block)
|
227
|
+
else
|
228
|
+
block.each_child_node(:send) do |child_node|
|
229
|
+
check_change_table_offense(arg, child_node)
|
230
|
+
end
|
231
|
+
end
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
def check_change_table_offense(receiver, node)
|
236
|
+
method_name = node.method_name
|
237
|
+
return if receiver != node.receiver &&
|
238
|
+
reversible_change_table_call?(node)
|
239
|
+
|
240
|
+
add_offense(
|
241
|
+
node,
|
242
|
+
message: format(MSG, action: "change_table(with #{method_name})")
|
243
|
+
)
|
244
|
+
end
|
245
|
+
|
246
|
+
def reversible_change_table_call?(node)
|
247
|
+
case node.method_name
|
248
|
+
when :change, :remove
|
249
|
+
false
|
250
|
+
when :change_default, :change_column_default, :change_table_comment,
|
251
|
+
:change_column_comment
|
252
|
+
all_hash_key?(node.arguments.last, :from, :to)
|
253
|
+
else
|
254
|
+
true
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
258
|
+
def within_change_method?(node)
|
259
|
+
node.each_ancestor(:def).any? do |ancestor|
|
260
|
+
ancestor.method?(:change)
|
261
|
+
end
|
262
|
+
end
|
263
|
+
|
264
|
+
def within_reversible_or_up_only_block?(node)
|
265
|
+
node.each_ancestor(:block).any? do |ancestor|
|
266
|
+
ancestor.block_type? &&
|
267
|
+
ancestor.send_node.method?(:reversible) ||
|
268
|
+
ancestor.send_node.method?(:up_only)
|
269
|
+
end
|
270
|
+
end
|
271
|
+
|
272
|
+
def all_hash_key?(args, *keys)
|
273
|
+
return false unless args&.hash_type?
|
274
|
+
|
275
|
+
hash_keys = args.keys.map do |key|
|
276
|
+
key.children.first.to_sym
|
277
|
+
end
|
278
|
+
|
279
|
+
hash_keys & keys == keys
|
280
|
+
end
|
281
|
+
end
|
282
|
+
end
|
283
|
+
end
|
284
|
+
end
|