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,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