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,94 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
6
+ # This cop checks that `if` and `only` (or `except`) are not used together
7
+ # as options of `skip_*` action filter.
8
+ #
9
+ # The `if` option will be ignored when `if` and `only` are used together.
10
+ # Similarly, the `except` option will be ignored when `if` and `except`
11
+ # are used together.
12
+ #
13
+ # @example
14
+ # # bad
15
+ # class MyPageController < ApplicationController
16
+ # skip_before_action :login_required,
17
+ # only: :show, if: :trusted_origin?
18
+ # end
19
+ #
20
+ # # good
21
+ # class MyPageController < ApplicationController
22
+ # skip_before_action :login_required,
23
+ # if: -> { trusted_origin? && action_name == "show" }
24
+ # end
25
+ #
26
+ # @example
27
+ # # bad
28
+ # class MyPageController < ApplicationController
29
+ # skip_before_action :login_required,
30
+ # except: :admin, if: :trusted_origin?
31
+ # end
32
+ #
33
+ # # good
34
+ # class MyPageController < ApplicationController
35
+ # skip_before_action :login_required,
36
+ # if: -> { trusted_origin? && action_name != "admin" }
37
+ # end
38
+ #
39
+ # @see https://api.rubyonrails.org/classes/AbstractController/Callbacks/ClassMethods.html#method-i-_normalize_callback_options
40
+ class IgnoredSkipActionFilterOption < Cop
41
+ MSG = <<~MSG.chomp.freeze
42
+ `%<ignore>s` option will be ignored when `%<prefer>s` and `%<ignore>s` are used together.
43
+ MSG
44
+
45
+ FILTERS = %w[
46
+ :skip_after_action
47
+ :skip_around_action
48
+ :skip_before_action
49
+ :skip_action_callback
50
+ ].freeze
51
+
52
+ def_node_matcher :filter_options, <<~PATTERN
53
+ (send
54
+ nil?
55
+ {#{FILTERS.join(' ')}}
56
+ _
57
+ $_)
58
+ PATTERN
59
+
60
+ def on_send(node)
61
+ options = filter_options(node)
62
+ return unless options
63
+ return unless options.hash_type?
64
+
65
+ options = options_hash(options)
66
+
67
+ if if_and_only?(options)
68
+ add_offense(options[:if],
69
+ message: format(MSG, prefer: :only, ignore: :if))
70
+ elsif if_and_except?(options)
71
+ add_offense(options[:except],
72
+ message: format(MSG, prefer: :if, ignore: :except))
73
+ end
74
+ end
75
+
76
+ private
77
+
78
+ def options_hash(options)
79
+ options.pairs
80
+ .select { |pair| pair.key.sym_type? }
81
+ .map { |pair| [pair.key.value, pair] }.to_h
82
+ end
83
+
84
+ def if_and_only?(options)
85
+ options.key?(:if) && options.key?(:only)
86
+ end
87
+
88
+ def if_and_except?(options)
89
+ options.key?(:if) && options.key?(:except)
90
+ end
91
+ end
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,246 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
6
+ # This cop looks for has_(one|many) and belongs_to associations where
7
+ # Active Record can't automatically determine the inverse association
8
+ # because of a scope or the options used. Using the blog with order scope
9
+ # example below, traversing the a Blog's association in both directions
10
+ # with `blog.posts.first.blog` would cause the `blog` to be loaded from
11
+ # the database twice.
12
+ #
13
+ # `:inverse_of` must be manually specified for Active Record to use the
14
+ # associated object in memory, or set to `false` to opt-out. Note that
15
+ # setting `nil` does not stop Active Record from trying to determine the
16
+ # inverse automatically, and is not considered a valid value for this.
17
+ #
18
+ # @example
19
+ # # good
20
+ # class Blog < ApplicationRecord
21
+ # has_many :posts
22
+ # end
23
+ #
24
+ # class Post < ApplicationRecord
25
+ # belongs_to :blog
26
+ # end
27
+ #
28
+ # @example
29
+ # # bad
30
+ # class Blog < ApplicationRecord
31
+ # has_many :posts, -> { order(published_at: :desc) }
32
+ # end
33
+ #
34
+ # class Post < ApplicationRecord
35
+ # belongs_to :blog
36
+ # end
37
+ #
38
+ # # good
39
+ # class Blog < ApplicationRecord
40
+ # has_many(:posts,
41
+ # -> { order(published_at: :desc) },
42
+ # inverse_of: :blog)
43
+ # end
44
+ #
45
+ # class Post < ApplicationRecord
46
+ # belongs_to :blog
47
+ # end
48
+ #
49
+ # # good
50
+ # class Blog < ApplicationRecord
51
+ # with_options inverse_of: :blog do
52
+ # has_many :posts, -> { order(published_at: :desc) }
53
+ # end
54
+ # end
55
+ #
56
+ # class Post < ApplicationRecord
57
+ # belongs_to :blog
58
+ # end
59
+ #
60
+ # # good
61
+ # # When you don't want to use the inverse association.
62
+ # class Blog < ApplicationRecord
63
+ # has_many(:posts,
64
+ # -> { order(published_at: :desc) },
65
+ # inverse_of: false)
66
+ # end
67
+ #
68
+ # @example
69
+ # # bad
70
+ # class Picture < ApplicationRecord
71
+ # belongs_to :imageable, polymorphic: true
72
+ # end
73
+ #
74
+ # class Employee < ApplicationRecord
75
+ # has_many :pictures, as: :imageable
76
+ # end
77
+ #
78
+ # class Product < ApplicationRecord
79
+ # has_many :pictures, as: :imageable
80
+ # end
81
+ #
82
+ # # good
83
+ # class Picture < ApplicationRecord
84
+ # belongs_to :imageable, polymorphic: true
85
+ # end
86
+ #
87
+ # class Employee < ApplicationRecord
88
+ # has_many :pictures, as: :imageable, inverse_of: :imageable
89
+ # end
90
+ #
91
+ # class Product < ApplicationRecord
92
+ # has_many :pictures, as: :imageable, inverse_of: :imageable
93
+ # end
94
+ #
95
+ # @example
96
+ # # bad
97
+ # # However, RuboCop can not detect this pattern...
98
+ # class Physician < ApplicationRecord
99
+ # has_many :appointments
100
+ # has_many :patients, through: :appointments
101
+ # end
102
+ #
103
+ # class Appointment < ApplicationRecord
104
+ # belongs_to :physician
105
+ # belongs_to :patient
106
+ # end
107
+ #
108
+ # class Patient < ApplicationRecord
109
+ # has_many :appointments
110
+ # has_many :physicians, through: :appointments
111
+ # end
112
+ #
113
+ # # good
114
+ # class Physician < ApplicationRecord
115
+ # has_many :appointments
116
+ # has_many :patients, through: :appointments
117
+ # end
118
+ #
119
+ # class Appointment < ApplicationRecord
120
+ # belongs_to :physician, inverse_of: :appointments
121
+ # belongs_to :patient, inverse_of: :appointments
122
+ # end
123
+ #
124
+ # class Patient < ApplicationRecord
125
+ # has_many :appointments
126
+ # has_many :physicians, through: :appointments
127
+ # end
128
+ #
129
+ # @see https://guides.rubyonrails.org/association_basics.html#bi-directional-associations
130
+ # @see https://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#module-ActiveRecord::Associations::ClassMethods-label-Setting+Inverses
131
+ class InverseOf < Cop
132
+ extend TargetRailsVersion
133
+
134
+ minimum_target_rails_version 4.1
135
+
136
+ SPECIFY_MSG = 'Specify an `:inverse_of` option.'
137
+ NIL_MSG = 'You specified `inverse_of: nil`, you probably meant to ' \
138
+ 'use `inverse_of: false`.'
139
+
140
+ def_node_matcher :association_recv_arguments, <<~PATTERN
141
+ (send $_ {:has_many :has_one :belongs_to} _ $...)
142
+ PATTERN
143
+
144
+ def_node_matcher :options_from_argument, <<~PATTERN
145
+ (hash $...)
146
+ PATTERN
147
+
148
+ def_node_matcher :conditions_option?, <<~PATTERN
149
+ (pair (sym :conditions) !nil)
150
+ PATTERN
151
+
152
+ def_node_matcher :through_option?, <<~PATTERN
153
+ (pair (sym :through) !nil)
154
+ PATTERN
155
+
156
+ def_node_matcher :polymorphic_option?, <<~PATTERN
157
+ (pair (sym :polymorphic) !nil)
158
+ PATTERN
159
+
160
+ def_node_matcher :as_option?, <<~PATTERN
161
+ (pair (sym :as) !nil)
162
+ PATTERN
163
+
164
+ def_node_matcher :foreign_key_option?, <<~PATTERN
165
+ (pair (sym :foreign_key) !nil)
166
+ PATTERN
167
+
168
+ def_node_matcher :inverse_of_option?, <<~PATTERN
169
+ (pair (sym :inverse_of) !nil)
170
+ PATTERN
171
+
172
+ def_node_matcher :inverse_of_nil_option?, <<~PATTERN
173
+ (pair (sym :inverse_of) nil)
174
+ PATTERN
175
+
176
+ def on_send(node)
177
+ recv, arguments = association_recv_arguments(node)
178
+ return unless arguments
179
+
180
+ with_options = with_options_arguments(recv, node)
181
+
182
+ options = arguments.concat(with_options).flat_map do |arg|
183
+ options_from_argument(arg)
184
+ end
185
+ return if options_ignoring_inverse_of?(options)
186
+
187
+ return unless scope?(arguments) ||
188
+ options_requiring_inverse_of?(options)
189
+
190
+ return if options_contain_inverse_of?(options)
191
+
192
+ add_offense(node, message: message(options), location: :selector)
193
+ end
194
+
195
+ def scope?(arguments)
196
+ arguments.any?(&:block_type?)
197
+ end
198
+
199
+ def options_requiring_inverse_of?(options)
200
+ required = options.any? do |opt|
201
+ conditions_option?(opt) ||
202
+ foreign_key_option?(opt)
203
+ end
204
+
205
+ return required if target_rails_version >= 5.2
206
+
207
+ required || options.any? { |opt| as_option?(opt) }
208
+ end
209
+
210
+ def options_ignoring_inverse_of?(options)
211
+ options.any? do |opt|
212
+ through_option?(opt) || polymorphic_option?(opt)
213
+ end
214
+ end
215
+
216
+ def options_contain_inverse_of?(options)
217
+ options.any? { |opt| inverse_of_option?(opt) }
218
+ end
219
+
220
+ def with_options_arguments(recv, node)
221
+ blocks = node.each_ancestor(:block).select do |block|
222
+ block.send_node.command?(:with_options) &&
223
+ same_context_in_with_options?(block.arguments.first, recv)
224
+ end
225
+ blocks.flat_map { |n| n.send_node.arguments }
226
+ end
227
+
228
+ def same_context_in_with_options?(arg, recv)
229
+ return true if arg.nil? && recv.nil?
230
+
231
+ arg && recv && arg.children[0] == recv.children[0]
232
+ end
233
+
234
+ private
235
+
236
+ def message(options)
237
+ if options.any? { |opt| inverse_of_nil_option?(opt) }
238
+ NIL_MSG
239
+ else
240
+ SPECIFY_MSG
241
+ end
242
+ end
243
+ end
244
+ end
245
+ end
246
+ end
@@ -0,0 +1,175 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
6
+ # This cop checks that methods specified in the filter's `only` or
7
+ # `except` options are defined within the same class or module.
8
+ #
9
+ # You can technically specify methods of superclass or methods added by
10
+ # mixins on the filter, but these can confuse developers. If you specify
11
+ # methods that are defined in other classes or modules, you should
12
+ # define the filter in that class or module.
13
+ #
14
+ # If you rely on behaviour defined in the superclass actions, you must
15
+ # remember to invoke `super` in the subclass actions.
16
+ #
17
+ # @example
18
+ # # bad
19
+ # class LoginController < ApplicationController
20
+ # before_action :require_login, only: %i[index settings logout]
21
+ #
22
+ # def index
23
+ # end
24
+ # end
25
+ #
26
+ # # good
27
+ # class LoginController < ApplicationController
28
+ # before_action :require_login, only: %i[index settings logout]
29
+ #
30
+ # def index
31
+ # end
32
+ #
33
+ # def settings
34
+ # end
35
+ #
36
+ # def logout
37
+ # end
38
+ # end
39
+ #
40
+ # @example
41
+ # # bad
42
+ # module FooMixin
43
+ # extend ActiveSupport::Concern
44
+ #
45
+ # included do
46
+ # before_action proc { authenticate }, only: :foo
47
+ # end
48
+ # end
49
+ #
50
+ # # good
51
+ # module FooMixin
52
+ # extend ActiveSupport::Concern
53
+ #
54
+ # included do
55
+ # before_action proc { authenticate }, only: :foo
56
+ # end
57
+ #
58
+ # def foo
59
+ # # something
60
+ # end
61
+ # end
62
+ #
63
+ # @example
64
+ # class ContentController < ApplicationController
65
+ # def update
66
+ # @content.update(content_attributes)
67
+ # end
68
+ # end
69
+ #
70
+ # class ArticlesController < ContentController
71
+ # before_action :load_article, only: [:update]
72
+ #
73
+ # # the cop requires this method, but it relies on behaviour defined
74
+ # # in the superclass, so needs to invoke `super`
75
+ # def update
76
+ # super
77
+ # end
78
+ #
79
+ # private
80
+ #
81
+ # def load_article
82
+ # @content = Article.find(params[:article_id])
83
+ # end
84
+ # end
85
+ class LexicallyScopedActionFilter < Cop
86
+ MSG = '%<action>s not explicitly defined on the %<type>s.'
87
+
88
+ FILTERS = %w[
89
+ :after_action
90
+ :append_after_action
91
+ :append_around_action
92
+ :append_before_action
93
+ :around_action
94
+ :before_action
95
+ :prepend_after_action
96
+ :prepend_around_action
97
+ :prepend_before_action
98
+ :skip_after_action
99
+ :skip_around_action
100
+ :skip_before_action
101
+ :skip_action_callback
102
+ ].freeze
103
+
104
+ def_node_matcher :only_or_except_filter_methods, <<~PATTERN
105
+ (send
106
+ nil?
107
+ {#{FILTERS.join(' ')}}
108
+ _
109
+ (hash
110
+ (pair
111
+ (sym {:only :except})
112
+ $_)))
113
+ PATTERN
114
+
115
+ def on_send(node)
116
+ methods_node = only_or_except_filter_methods(node)
117
+ return unless methods_node
118
+
119
+ parent = node.each_ancestor(:class, :module).first
120
+ return unless parent
121
+
122
+ block = parent.each_child_node(:begin).first
123
+ return unless block
124
+
125
+ defined_methods = block.each_child_node(:def).map(&:method_name)
126
+ methods = array_values(methods_node).reject do |method|
127
+ defined_methods.include?(method)
128
+ end
129
+
130
+ message = message(methods, parent)
131
+ add_offense(node, message: message) unless methods.empty?
132
+ end
133
+
134
+ private
135
+
136
+ # @param node [RuboCop::AST::Node]
137
+ # @return [Array<Symbol>]
138
+ def array_values(node) # rubocop:disable Metrics/MethodLength
139
+ case node.type
140
+ when :str
141
+ [node.str_content.to_sym]
142
+ when :sym
143
+ [node.value]
144
+ when :array
145
+ node.values.map do |v|
146
+ case v.type
147
+ when :str
148
+ v.str_content.to_sym
149
+ when :sym
150
+ v.value
151
+ end
152
+ end.compact
153
+ else
154
+ []
155
+ end
156
+ end
157
+
158
+ # @param methods [Array<String>]
159
+ # @param parent [RuboCop::AST::Node]
160
+ # @return [String]
161
+ def message(methods, parent)
162
+ if methods.size == 1
163
+ format(MSG,
164
+ action: "`#{methods[0]}` is",
165
+ type: parent.type)
166
+ else
167
+ format(MSG,
168
+ action: "`#{methods.join('`, `')}` are",
169
+ type: parent.type)
170
+ end
171
+ end
172
+ end
173
+ end
174
+ end
175
+ end