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.
Files changed (69) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.txt +20 -0
  3. data/README.md +92 -0
  4. data/bin/setup +7 -0
  5. data/config/default.yml +510 -0
  6. data/lib/rubocop-rails.rb +12 -0
  7. data/lib/rubocop/cop/mixin/target_rails_version.rb +16 -0
  8. data/lib/rubocop/cop/rails/action_filter.rb +111 -0
  9. data/lib/rubocop/cop/rails/active_record_aliases.rb +48 -0
  10. data/lib/rubocop/cop/rails/active_record_override.rb +82 -0
  11. data/lib/rubocop/cop/rails/active_support_aliases.rb +69 -0
  12. data/lib/rubocop/cop/rails/application_controller.rb +36 -0
  13. data/lib/rubocop/cop/rails/application_job.rb +40 -0
  14. data/lib/rubocop/cop/rails/application_mailer.rb +40 -0
  15. data/lib/rubocop/cop/rails/application_record.rb +40 -0
  16. data/lib/rubocop/cop/rails/assert_not.rb +44 -0
  17. data/lib/rubocop/cop/rails/belongs_to.rb +102 -0
  18. data/lib/rubocop/cop/rails/blank.rb +164 -0
  19. data/lib/rubocop/cop/rails/bulk_change_table.rb +293 -0
  20. data/lib/rubocop/cop/rails/create_table_with_timestamps.rb +91 -0
  21. data/lib/rubocop/cop/rails/date.rb +161 -0
  22. data/lib/rubocop/cop/rails/delegate.rb +132 -0
  23. data/lib/rubocop/cop/rails/delegate_allow_blank.rb +37 -0
  24. data/lib/rubocop/cop/rails/dynamic_find_by.rb +91 -0
  25. data/lib/rubocop/cop/rails/enum_hash.rb +75 -0
  26. data/lib/rubocop/cop/rails/enum_uniqueness.rb +65 -0
  27. data/lib/rubocop/cop/rails/environment_comparison.rb +68 -0
  28. data/lib/rubocop/cop/rails/exit.rb +67 -0
  29. data/lib/rubocop/cop/rails/file_path.rb +108 -0
  30. data/lib/rubocop/cop/rails/find_by.rb +55 -0
  31. data/lib/rubocop/cop/rails/find_each.rb +51 -0
  32. data/lib/rubocop/cop/rails/has_and_belongs_to_many.rb +25 -0
  33. data/lib/rubocop/cop/rails/has_many_or_has_one_dependent.rb +106 -0
  34. data/lib/rubocop/cop/rails/helper_instance_variable.rb +39 -0
  35. data/lib/rubocop/cop/rails/http_positional_arguments.rb +117 -0
  36. data/lib/rubocop/cop/rails/http_status.rb +160 -0
  37. data/lib/rubocop/cop/rails/ignored_skip_action_filter_option.rb +94 -0
  38. data/lib/rubocop/cop/rails/inverse_of.rb +246 -0
  39. data/lib/rubocop/cop/rails/lexically_scoped_action_filter.rb +175 -0
  40. data/lib/rubocop/cop/rails/link_to_blank.rb +98 -0
  41. data/lib/rubocop/cop/rails/not_null_column.rb +67 -0
  42. data/lib/rubocop/cop/rails/output.rb +49 -0
  43. data/lib/rubocop/cop/rails/output_safety.rb +99 -0
  44. data/lib/rubocop/cop/rails/pluralization_grammar.rb +107 -0
  45. data/lib/rubocop/cop/rails/presence.rb +148 -0
  46. data/lib/rubocop/cop/rails/present.rb +153 -0
  47. data/lib/rubocop/cop/rails/rake_environment.rb +91 -0
  48. data/lib/rubocop/cop/rails/read_write_attribute.rb +74 -0
  49. data/lib/rubocop/cop/rails/redundant_allow_nil.rb +111 -0
  50. data/lib/rubocop/cop/rails/redundant_receiver_in_with_options.rb +136 -0
  51. data/lib/rubocop/cop/rails/reflection_class_name.rb +37 -0
  52. data/lib/rubocop/cop/rails/refute_methods.rb +76 -0
  53. data/lib/rubocop/cop/rails/relative_date_constant.rb +102 -0
  54. data/lib/rubocop/cop/rails/request_referer.rb +56 -0
  55. data/lib/rubocop/cop/rails/reversible_migration.rb +284 -0
  56. data/lib/rubocop/cop/rails/safe_navigation.rb +85 -0
  57. data/lib/rubocop/cop/rails/safe_navigation_with_blank.rb +48 -0
  58. data/lib/rubocop/cop/rails/save_bang.rb +331 -0
  59. data/lib/rubocop/cop/rails/scope_args.rb +29 -0
  60. data/lib/rubocop/cop/rails/skips_model_validations.rb +87 -0
  61. data/lib/rubocop/cop/rails/time_zone.rb +249 -0
  62. data/lib/rubocop/cop/rails/uniq_before_pluck.rb +105 -0
  63. data/lib/rubocop/cop/rails/unknown_env.rb +84 -0
  64. data/lib/rubocop/cop/rails/validation.rb +147 -0
  65. data/lib/rubocop/cop/rails_cops.rb +61 -0
  66. data/lib/rubocop/rails.rb +12 -0
  67. data/lib/rubocop/rails/inject.rb +18 -0
  68. data/lib/rubocop/rails/version.rb +10 -0
  69. metadata +148 -0
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
6
+ # This cop checks for scope calls where it was passed
7
+ # a method (usually a scope) instead of a lambda/proc.
8
+ #
9
+ # @example
10
+ #
11
+ # # bad
12
+ # scope :something, where(something: true)
13
+ #
14
+ # # good
15
+ # scope :something, -> { where(something: true) }
16
+ class ScopeArgs < Cop
17
+ MSG = 'Use `lambda`/`proc` instead of a plain method call.'
18
+
19
+ def_node_matcher :scope?, '(send nil? :scope _ $send)'
20
+
21
+ def on_send(node)
22
+ scope?(node) do |second_arg|
23
+ add_offense(second_arg)
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
6
+ # This cop checks for the use of methods which skip
7
+ # validations which are listed in
8
+ # https://guides.rubyonrails.org/active_record_validations.html#skipping-validations
9
+ #
10
+ # Methods may be ignored from this rule by configuring a `Whitelist`.
11
+ #
12
+ # @example
13
+ # # bad
14
+ # Article.first.decrement!(:view_count)
15
+ # DiscussionBoard.decrement_counter(:post_count, 5)
16
+ # Article.first.increment!(:view_count)
17
+ # DiscussionBoard.increment_counter(:post_count, 5)
18
+ # person.toggle :active
19
+ # product.touch
20
+ # Billing.update_all("category = 'authorized', author = 'David'")
21
+ # user.update_attribute(:website, 'example.com')
22
+ # user.update_columns(last_request_at: Time.current)
23
+ # Post.update_counters 5, comment_count: -1, action_count: 1
24
+ #
25
+ # # good
26
+ # user.update(website: 'example.com')
27
+ # FileUtils.touch('file')
28
+ #
29
+ # @example Whitelist: ["touch"]
30
+ # # bad
31
+ # DiscussionBoard.decrement_counter(:post_count, 5)
32
+ # DiscussionBoard.increment_counter(:post_count, 5)
33
+ # person.toggle :active
34
+ #
35
+ # # good
36
+ # user.touch
37
+ #
38
+ class SkipsModelValidations < Cop
39
+ MSG = 'Avoid using `%<method>s` because it skips validations.'
40
+
41
+ METHODS_WITH_ARGUMENTS = %w[decrement!
42
+ decrement_counter
43
+ increment!
44
+ increment_counter
45
+ toggle!
46
+ update_all
47
+ update_attribute
48
+ update_column
49
+ update_columns
50
+ update_counters].freeze
51
+
52
+ def_node_matcher :good_touch?, <<~PATTERN
53
+ (send (const nil? :FileUtils) :touch ...)
54
+ PATTERN
55
+
56
+ def on_send(node)
57
+ return if whitelist.include?(node.method_name.to_s)
58
+ return unless blacklist.include?(node.method_name.to_s)
59
+ return if allowed_method?(node)
60
+ return if good_touch?(node)
61
+
62
+ add_offense(node, location: :selector)
63
+ end
64
+ alias on_csend on_send
65
+
66
+ private
67
+
68
+ def message(node)
69
+ format(MSG, method: node.method_name)
70
+ end
71
+
72
+ def allowed_method?(node)
73
+ METHODS_WITH_ARGUMENTS.include?(node.method_name.to_s) &&
74
+ !node.arguments?
75
+ end
76
+
77
+ def blacklist
78
+ cop_config['Blacklist'] || []
79
+ end
80
+
81
+ def whitelist
82
+ cop_config['Whitelist'] || []
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,249 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
6
+ # This cop checks for the use of Time methods without zone.
7
+ #
8
+ # Built on top of Ruby on Rails style guide (https://rails.rubystyle.guide#time)
9
+ # and the article http://danilenko.org/2012/7/6/rails_timezones/
10
+ #
11
+ # Two styles are supported for this cop. When EnforcedStyle is 'strict'
12
+ # then only use of Time.zone is allowed.
13
+ #
14
+ # When EnforcedStyle is 'flexible' then it's also allowed
15
+ # to use Time.in_time_zone.
16
+ #
17
+ # @example EnforcedStyle: strict
18
+ # # `strict` means that `Time` should be used with `zone`.
19
+ #
20
+ # # bad
21
+ # Time.now
22
+ # Time.parse('2015-03-02 19:05:37')
23
+ #
24
+ # # bad
25
+ # Time.current
26
+ # Time.at(timestamp).in_time_zone
27
+ #
28
+ # # good
29
+ # Time.zone.now
30
+ # Time.zone.parse('2015-03-02 19:05:37')
31
+ #
32
+ # @example EnforcedStyle: flexible (default)
33
+ # # `flexible` allows usage of `in_time_zone` instead of `zone`.
34
+ #
35
+ # # bad
36
+ # Time.now
37
+ # Time.parse('2015-03-02 19:05:37')
38
+ #
39
+ # # good
40
+ # Time.zone.now
41
+ # Time.zone.parse('2015-03-02 19:05:37')
42
+ #
43
+ # # good
44
+ # Time.current
45
+ # Time.at(timestamp).in_time_zone
46
+ class TimeZone < Cop
47
+ include ConfigurableEnforcedStyle
48
+
49
+ MSG = 'Do not use `%<current>s` without zone. Use `%<prefer>s` ' \
50
+ 'instead.'
51
+
52
+ MSG_ACCEPTABLE = 'Do not use `%<current>s` without zone. ' \
53
+ 'Use one of %<prefer>s instead.'
54
+
55
+ MSG_LOCALTIME = 'Do not use `Time.localtime` without ' \
56
+ 'offset or zone.'
57
+
58
+ GOOD_METHODS = %i[zone zone_default find_zone find_zone!].freeze
59
+
60
+ DANGEROUS_METHODS = %i[now local new parse at current].freeze
61
+
62
+ ACCEPTED_METHODS = %i[in_time_zone utc getlocal xmlschema iso8601
63
+ jisx0301 rfc3339 httpdate to_i to_f].freeze
64
+
65
+ def on_const(node)
66
+ mod, klass = *node
67
+ # we should only check core classes
68
+ # (`Time` or `::Time`)
69
+ return unless (mod.nil? || mod.cbase_type?) && method_send?(node)
70
+
71
+ check_time_node(klass, node.parent) if klass == :Time
72
+ end
73
+
74
+ def autocorrect(node)
75
+ lambda do |corrector|
76
+ # add `.zone`: `Time.at` => `Time.zone.at`
77
+ corrector.insert_after(node.children[0].source_range, '.zone')
78
+
79
+ case node.method_name
80
+ when :current
81
+ # replace `Time.zone.current` => `Time.zone.now`
82
+ corrector.replace(node.loc.selector, 'now')
83
+ when :new
84
+ autocorrect_time_new(node, corrector)
85
+ end
86
+
87
+ # prefer `Time` over `DateTime` class
88
+ if strict?
89
+ corrector.replace(node.children.first.source_range, 'Time')
90
+ end
91
+ remove_redundant_in_time_zone(corrector, node)
92
+ end
93
+ end
94
+
95
+ private
96
+
97
+ def autocorrect_time_new(node, corrector)
98
+ if node.arguments?
99
+ corrector.replace(node.loc.selector, 'local')
100
+ else
101
+ corrector.replace(node.loc.selector, 'now')
102
+ end
103
+ end
104
+
105
+ # remove redundant `.in_time_zone` from `Time.zone.now.in_time_zone`
106
+ def remove_redundant_in_time_zone(corrector, node)
107
+ time_methods_called = extract_method_chain(node)
108
+ return unless time_methods_called.include?(:in_time_zone) ||
109
+ time_methods_called.include?(:zone)
110
+
111
+ while node&.send_type?
112
+ if node.children.last == :in_time_zone
113
+ in_time_zone_with_dot =
114
+ node.loc.selector.adjust(begin_pos: -1)
115
+ corrector.remove(in_time_zone_with_dot)
116
+ end
117
+ node = node.parent
118
+ end
119
+ end
120
+
121
+ def check_time_node(klass, node)
122
+ chain = extract_method_chain(node)
123
+ return if not_danger_chain?(chain)
124
+
125
+ return check_localtime(node) if need_check_localtime?(chain)
126
+
127
+ method_name = (chain & DANGEROUS_METHODS).join('.')
128
+
129
+ return if offset_provided?(node)
130
+
131
+ message = build_message(klass, method_name, node)
132
+
133
+ add_offense(node, location: :selector, message: message)
134
+ end
135
+
136
+ def build_message(klass, method_name, node)
137
+ if flexible?
138
+ format(
139
+ MSG_ACCEPTABLE,
140
+ current: "#{klass}.#{method_name}",
141
+ prefer: acceptable_methods(klass, method_name, node).join(', ')
142
+ )
143
+ else
144
+ safe_method_name = safe_method(method_name, node)
145
+ format(MSG,
146
+ current: "#{klass}.#{method_name}",
147
+ prefer: "Time.zone.#{safe_method_name}")
148
+ end
149
+ end
150
+
151
+ def extract_method_chain(node)
152
+ chain = []
153
+ while !node.nil? && node.send_type?
154
+ chain << node.method_name if method_from_time_class?(node)
155
+ node = node.parent
156
+ end
157
+ chain
158
+ end
159
+
160
+ # Only add the method to the chain if the method being
161
+ # called is part of the time class.
162
+ def method_from_time_class?(node)
163
+ receiver, method_name, *_args = *node
164
+ if (receiver.is_a? RuboCop::AST::Node) && !receiver.cbase_type?
165
+ method_from_time_class?(receiver)
166
+ else
167
+ method_name == :Time
168
+ end
169
+ end
170
+
171
+ # checks that parent node of send_type
172
+ # and receiver is the given node
173
+ def method_send?(node)
174
+ return false unless node.parent&.send_type?
175
+
176
+ node.parent.receiver == node
177
+ end
178
+
179
+ def safe_method(method_name, node)
180
+ if %w[new current].include?(method_name)
181
+ node.arguments? ? 'local' : 'now'
182
+ else
183
+ method_name
184
+ end
185
+ end
186
+
187
+ def check_localtime(node)
188
+ selector_node = node
189
+
190
+ while node&.send_type?
191
+ break if node.method?(:localtime)
192
+
193
+ node = node.parent
194
+ end
195
+
196
+ return if node.arguments?
197
+
198
+ add_offense(selector_node,
199
+ location: :selector, message: MSG_LOCALTIME)
200
+ end
201
+
202
+ def not_danger_chain?(chain)
203
+ (chain & DANGEROUS_METHODS).empty? || !(chain & good_methods).empty?
204
+ end
205
+
206
+ def need_check_localtime?(chain)
207
+ flexible? && chain.include?(:localtime)
208
+ end
209
+
210
+ def flexible?
211
+ style == :flexible
212
+ end
213
+
214
+ def strict?
215
+ style == :strict
216
+ end
217
+
218
+ def good_methods
219
+ if strict?
220
+ GOOD_METHODS
221
+ else
222
+ GOOD_METHODS + [:current] + ACCEPTED_METHODS
223
+ end
224
+ end
225
+
226
+ def acceptable_methods(klass, method_name, node)
227
+ acceptable = [
228
+ "`Time.zone.#{safe_method(method_name, node)}`",
229
+ "`#{klass}.current`"
230
+ ]
231
+
232
+ ACCEPTED_METHODS.each do |am|
233
+ acceptable << "`#{klass}.#{method_name}.#{am}`"
234
+ end
235
+
236
+ acceptable
237
+ end
238
+
239
+ # Time.new can be called with a time zone offset
240
+ # When it is, that should be considered safe
241
+ # Example:
242
+ # Time.new(1988, 3, 15, 3, 0, 0, "-05:00")
243
+ def offset_provided?(node)
244
+ node.arguments.size >= 7
245
+ end
246
+ end
247
+ end
248
+ end
249
+ end
@@ -0,0 +1,105 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
6
+ # Prefer the use of uniq (or distinct), before pluck instead of after.
7
+ #
8
+ # The use of uniq before pluck is preferred because it executes within
9
+ # the database.
10
+ #
11
+ # This cop has two different enforcement modes. When the EnforcedStyle
12
+ # is conservative (the default) then only calls to pluck on a constant
13
+ # (i.e. a model class) before uniq are added as offenses.
14
+ #
15
+ # When the EnforcedStyle is aggressive then all calls to pluck before
16
+ # uniq are added as offenses. This may lead to false positives as the cop
17
+ # cannot distinguish between calls to pluck on an ActiveRecord::Relation
18
+ # vs a call to pluck on an ActiveRecord::Associations::CollectionProxy.
19
+ #
20
+ # Autocorrect is disabled by default for this cop since it may generate
21
+ # false positives.
22
+ #
23
+ # @example EnforcedStyle: conservative (default)
24
+ # # bad
25
+ # Model.pluck(:id).uniq
26
+ #
27
+ # # good
28
+ # Model.uniq.pluck(:id)
29
+ #
30
+ # @example EnforcedStyle: aggressive
31
+ # # bad
32
+ # # this will return a Relation that pluck is called on
33
+ # Model.where(cond: true).pluck(:id).uniq
34
+ #
35
+ # # bad
36
+ # # an association on an instance will return a CollectionProxy
37
+ # instance.assoc.pluck(:id).uniq
38
+ #
39
+ # # bad
40
+ # Model.pluck(:id).uniq
41
+ #
42
+ # # good
43
+ # Model.uniq.pluck(:id)
44
+ #
45
+ class UniqBeforePluck < RuboCop::Cop::Cop
46
+ include ConfigurableEnforcedStyle
47
+ include RangeHelp
48
+
49
+ MSG = 'Use `%<method>s` before `pluck`.'
50
+ NEWLINE = "\n"
51
+ PATTERN = '[!^block (send (send %<type>s :pluck ...) ' \
52
+ '${:uniq :distinct} ...)]'
53
+
54
+ def_node_matcher :conservative_node_match,
55
+ format(PATTERN, type: 'const')
56
+
57
+ def_node_matcher :aggressive_node_match,
58
+ format(PATTERN, type: '_')
59
+
60
+ def on_send(node)
61
+ method = if style == :conservative
62
+ conservative_node_match(node)
63
+ else
64
+ aggressive_node_match(node)
65
+ end
66
+
67
+ return unless method
68
+
69
+ add_offense(node, location: :selector,
70
+ message: format(MSG, method: method))
71
+ end
72
+
73
+ def autocorrect(node)
74
+ lambda do |corrector|
75
+ method = node.method_name
76
+
77
+ corrector.remove(dot_method_with_whitespace(method, node))
78
+ corrector.insert_before(node.receiver.loc.dot.begin, ".#{method}")
79
+ end
80
+ end
81
+
82
+ private
83
+
84
+ def style_parameter_name
85
+ 'EnforcedStyle'
86
+ end
87
+
88
+ def dot_method_with_whitespace(method, node)
89
+ range_between(dot_method_begin_pos(method, node),
90
+ node.loc.selector.end_pos)
91
+ end
92
+
93
+ def dot_method_begin_pos(method, node)
94
+ lines = node.source.split(NEWLINE)
95
+
96
+ if lines.last.strip == ".#{method}"
97
+ node.source.rindex(NEWLINE)
98
+ else
99
+ node.loc.dot.begin_pos
100
+ end
101
+ end
102
+ end
103
+ end
104
+ end
105
+ end