rubocop-rails 2.4.1

Sign up to get free protection for your applications and to get access to all the features.
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