rubocop-rails 2.0.0

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