rubocop-rails 2.0.0

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 (64) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.txt +20 -0
  3. data/README.md +73 -0
  4. data/bin/setup +7 -0
  5. data/config/default.yml +466 -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 +117 -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_job.rb +40 -0
  13. data/lib/rubocop/cop/rails/application_record.rb +40 -0
  14. data/lib/rubocop/cop/rails/assert_not.rb +44 -0
  15. data/lib/rubocop/cop/rails/belongs_to.rb +102 -0
  16. data/lib/rubocop/cop/rails/blank.rb +164 -0
  17. data/lib/rubocop/cop/rails/bulk_change_table.rb +289 -0
  18. data/lib/rubocop/cop/rails/create_table_with_timestamps.rb +91 -0
  19. data/lib/rubocop/cop/rails/date.rb +161 -0
  20. data/lib/rubocop/cop/rails/delegate.rb +132 -0
  21. data/lib/rubocop/cop/rails/delegate_allow_blank.rb +37 -0
  22. data/lib/rubocop/cop/rails/dynamic_find_by.rb +91 -0
  23. data/lib/rubocop/cop/rails/enum_uniqueness.rb +45 -0
  24. data/lib/rubocop/cop/rails/environment_comparison.rb +68 -0
  25. data/lib/rubocop/cop/rails/exit.rb +67 -0
  26. data/lib/rubocop/cop/rails/file_path.rb +108 -0
  27. data/lib/rubocop/cop/rails/find_by.rb +55 -0
  28. data/lib/rubocop/cop/rails/find_each.rb +51 -0
  29. data/lib/rubocop/cop/rails/has_and_belongs_to_many.rb +25 -0
  30. data/lib/rubocop/cop/rails/has_many_or_has_one_dependent.rb +106 -0
  31. data/lib/rubocop/cop/rails/helper_instance_variable.rb +39 -0
  32. data/lib/rubocop/cop/rails/http_positional_arguments.rb +117 -0
  33. data/lib/rubocop/cop/rails/http_status.rb +160 -0
  34. data/lib/rubocop/cop/rails/ignored_skip_action_filter_option.rb +94 -0
  35. data/lib/rubocop/cop/rails/inverse_of.rb +246 -0
  36. data/lib/rubocop/cop/rails/lexically_scoped_action_filter.rb +175 -0
  37. data/lib/rubocop/cop/rails/link_to_blank.rb +98 -0
  38. data/lib/rubocop/cop/rails/not_null_column.rb +67 -0
  39. data/lib/rubocop/cop/rails/output.rb +49 -0
  40. data/lib/rubocop/cop/rails/output_safety.rb +99 -0
  41. data/lib/rubocop/cop/rails/pluralization_grammar.rb +107 -0
  42. data/lib/rubocop/cop/rails/presence.rb +124 -0
  43. data/lib/rubocop/cop/rails/present.rb +153 -0
  44. data/lib/rubocop/cop/rails/read_write_attribute.rb +74 -0
  45. data/lib/rubocop/cop/rails/redundant_allow_nil.rb +111 -0
  46. data/lib/rubocop/cop/rails/redundant_receiver_in_with_options.rb +136 -0
  47. data/lib/rubocop/cop/rails/reflection_class_name.rb +37 -0
  48. data/lib/rubocop/cop/rails/refute_methods.rb +76 -0
  49. data/lib/rubocop/cop/rails/relative_date_constant.rb +93 -0
  50. data/lib/rubocop/cop/rails/request_referer.rb +56 -0
  51. data/lib/rubocop/cop/rails/reversible_migration.rb +286 -0
  52. data/lib/rubocop/cop/rails/safe_navigation.rb +87 -0
  53. data/lib/rubocop/cop/rails/save_bang.rb +316 -0
  54. data/lib/rubocop/cop/rails/scope_args.rb +29 -0
  55. data/lib/rubocop/cop/rails/skips_model_validations.rb +87 -0
  56. data/lib/rubocop/cop/rails/time_zone.rb +238 -0
  57. data/lib/rubocop/cop/rails/uniq_before_pluck.rb +105 -0
  58. data/lib/rubocop/cop/rails/unknown_env.rb +63 -0
  59. data/lib/rubocop/cop/rails/validation.rb +109 -0
  60. data/lib/rubocop/cop/rails_cops.rb +64 -0
  61. data/lib/rubocop/rails.rb +12 -0
  62. data/lib/rubocop/rails/inject.rb +18 -0
  63. data/lib/rubocop/rails/version.rb +10 -0
  64. metadata +143 -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,238 @@
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://github.com/rubocop-hq/rails-style-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
+ TIMECLASSES = %i[Time DateTime].freeze
59
+
60
+ GOOD_METHODS = %i[zone zone_default find_zone find_zone!].freeze
61
+
62
+ DANGEROUS_METHODS = %i[now local new parse at current].freeze
63
+
64
+ ACCEPTED_METHODS = %i[in_time_zone utc getlocal xmlschema iso8601
65
+ jisx0301 rfc3339 httpdate to_i to_f].freeze
66
+
67
+ def on_const(node)
68
+ mod, klass = *node
69
+ # we should only check core classes
70
+ # (`DateTime`, `Time`, `::DateTime` or `::Time`)
71
+ return unless (mod.nil? || mod.cbase_type?) && method_send?(node)
72
+
73
+ check_time_node(klass, node.parent) if TIMECLASSES.include?(klass)
74
+ end
75
+
76
+ def autocorrect(node)
77
+ lambda do |corrector|
78
+ # add `.zone`: `Time.at` => `Time.zone.at`
79
+ corrector.insert_after(node.children[0].source_range, '.zone')
80
+ # replace `Time.zone.current` => `Time.zone.now`
81
+ if node.method_name == :current
82
+ corrector.replace(node.loc.selector, 'now')
83
+ end
84
+ # prefer `Time` over `DateTime` class
85
+ if strict?
86
+ corrector.replace(node.children.first.source_range, 'Time')
87
+ end
88
+ remove_redundant_in_time_zone(corrector, node)
89
+ end
90
+ end
91
+
92
+ private
93
+
94
+ # remove redundant `.in_time_zone` from `Time.zone.now.in_time_zone`
95
+ def remove_redundant_in_time_zone(corrector, node)
96
+ time_methods_called = extract_method_chain(node)
97
+ return unless time_methods_called.include?(:in_time_zone) ||
98
+ time_methods_called.include?(:zone)
99
+
100
+ while node&.send_type?
101
+ if node.children.last == :in_time_zone
102
+ in_time_zone_with_dot =
103
+ node.loc.selector.adjust(begin_pos: -1)
104
+ corrector.remove(in_time_zone_with_dot)
105
+ end
106
+ node = node.parent
107
+ end
108
+ end
109
+
110
+ def check_time_node(klass, node)
111
+ chain = extract_method_chain(node)
112
+ return if not_danger_chain?(chain)
113
+
114
+ return check_localtime(node) if need_check_localtime?(chain)
115
+
116
+ method_name = (chain & DANGEROUS_METHODS).join('.')
117
+
118
+ return if offset_provided?(node)
119
+
120
+ message = build_message(klass, method_name, node)
121
+
122
+ add_offense(node, location: :selector, message: message)
123
+ end
124
+
125
+ def build_message(klass, method_name, node)
126
+ if flexible?
127
+ format(
128
+ MSG_ACCEPTABLE,
129
+ current: "#{klass}.#{method_name}",
130
+ prefer: acceptable_methods(klass, method_name, node).join(', ')
131
+ )
132
+ else
133
+ safe_method_name = safe_method(method_name, node)
134
+ format(MSG,
135
+ current: "#{klass}.#{method_name}",
136
+ prefer: "Time.zone.#{safe_method_name}")
137
+ end
138
+ end
139
+
140
+ def extract_method_chain(node)
141
+ chain = []
142
+ while !node.nil? && node.send_type?
143
+ chain << node.method_name if method_from_time_class?(node)
144
+ node = node.parent
145
+ end
146
+ chain
147
+ end
148
+
149
+ # Only add the method to the chain if the method being
150
+ # called is part of the time class.
151
+ def method_from_time_class?(node)
152
+ receiver, method_name, *_args = *node
153
+ if (receiver.is_a? RuboCop::AST::Node) && !receiver.cbase_type?
154
+ method_from_time_class?(receiver)
155
+ else
156
+ TIMECLASSES.include?(method_name)
157
+ end
158
+ end
159
+
160
+ # checks that parent node of send_type
161
+ # and receiver is the given node
162
+ def method_send?(node)
163
+ return false unless node.parent&.send_type?
164
+
165
+ node.parent.receiver == node
166
+ end
167
+
168
+ def safe_method(method_name, node)
169
+ if %w[new current].include?(method_name)
170
+ node.arguments? ? 'local' : 'now'
171
+ else
172
+ method_name
173
+ end
174
+ end
175
+
176
+ def check_localtime(node)
177
+ selector_node = node
178
+
179
+ while node&.send_type?
180
+ break if node.method_name == :localtime
181
+
182
+ node = node.parent
183
+ end
184
+
185
+ return if node.arguments?
186
+
187
+ add_offense(selector_node,
188
+ location: :selector, message: MSG_LOCALTIME)
189
+ end
190
+
191
+ def not_danger_chain?(chain)
192
+ (chain & DANGEROUS_METHODS).empty? || !(chain & good_methods).empty?
193
+ end
194
+
195
+ def need_check_localtime?(chain)
196
+ flexible? && chain.include?(:localtime)
197
+ end
198
+
199
+ def flexible?
200
+ style == :flexible
201
+ end
202
+
203
+ def strict?
204
+ style == :strict
205
+ end
206
+
207
+ def good_methods
208
+ if strict?
209
+ GOOD_METHODS
210
+ else
211
+ GOOD_METHODS + [:current] + ACCEPTED_METHODS
212
+ end
213
+ end
214
+
215
+ def acceptable_methods(klass, method_name, node)
216
+ acceptable = [
217
+ "`Time.zone.#{safe_method(method_name, node)}`",
218
+ "`#{klass}.current`"
219
+ ]
220
+
221
+ ACCEPTED_METHODS.each do |am|
222
+ acceptable << "`#{klass}.#{method_name}.#{am}`"
223
+ end
224
+
225
+ acceptable
226
+ end
227
+
228
+ # Time.new can be called with a time zone offset
229
+ # When it is, that should be considered safe
230
+ # Example:
231
+ # Time.new(1988, 3, 15, 3, 0, 0, "-05:00")
232
+ def offset_provided?(node)
233
+ node.arguments.size >= 7
234
+ end
235
+ end
236
+ end
237
+ end
238
+ 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