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,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
@@ -0,0 +1,98 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
6
+ # This cop checks for calls to `link_to` that contain a
7
+ # `target: '_blank'` but no `rel: 'noopener'`. This can be a security
8
+ # risk as the loaded page will have control over the previous page
9
+ # and could change its location for phishing purposes.
10
+ #
11
+ # The option `rel: 'noreferrer'` also blocks this behavior
12
+ # and removes the http-referrer header.
13
+ #
14
+ # @example
15
+ # # bad
16
+ # link_to 'Click here', url, target: '_blank'
17
+ #
18
+ # # good
19
+ # link_to 'Click here', url, target: '_blank', rel: 'noopener'
20
+ #
21
+ # # good
22
+ # link_to 'Click here', url, target: '_blank', rel: 'noreferrer'
23
+ class LinkToBlank < Cop
24
+ MSG = 'Specify a `:rel` option containing noopener.'
25
+
26
+ def_node_matcher :blank_target?, <<-PATTERN
27
+ (pair {(sym :target) (str "target")} {(str "_blank") (sym :_blank)})
28
+ PATTERN
29
+
30
+ def_node_matcher :includes_noopener?, <<-PATTERN
31
+ (pair {(sym :rel) (str "rel")} ({str sym} #contains_noopener?))
32
+ PATTERN
33
+
34
+ def_node_matcher :rel_node?, <<-PATTERN
35
+ (pair {(sym :rel) (str "rel")} (str _))
36
+ PATTERN
37
+
38
+ def on_send(node)
39
+ return unless node.method?(:link_to)
40
+
41
+ option_nodes = node.each_child_node(:hash)
42
+
43
+ option_nodes.map(&:children).each do |options|
44
+ blank = options.find { |o| blank_target?(o) }
45
+ if blank && options.none? { |o| includes_noopener?(o) }
46
+ add_offense(blank)
47
+ end
48
+ end
49
+ end
50
+
51
+ def autocorrect(node)
52
+ lambda do |corrector|
53
+ send_node = node.parent.parent
54
+
55
+ option_nodes = send_node.each_child_node(:hash)
56
+ rel_node = nil
57
+ option_nodes.map(&:children).each do |options|
58
+ rel_node ||= options.find { |o| rel_node?(o) }
59
+ end
60
+
61
+ if rel_node
62
+ append_to_rel(rel_node, corrector)
63
+ else
64
+ add_rel(send_node, node, corrector)
65
+ end
66
+ end
67
+ end
68
+
69
+ private
70
+
71
+ def append_to_rel(rel_node, corrector)
72
+ existing_rel = rel_node.children.last.value
73
+ str_range = rel_node.children.last.loc.expression.adjust(
74
+ begin_pos: 1,
75
+ end_pos: -1
76
+ )
77
+ corrector.replace(str_range, "#{existing_rel} noopener")
78
+ end
79
+
80
+ def add_rel(send_node, offence_node, corrector)
81
+ opening_quote = offence_node.children.last.source[0]
82
+ closing_quote = opening_quote == ':' ? '' : opening_quote
83
+ new_rel_exp = ", rel: #{opening_quote}noopener#{closing_quote}"
84
+ range = send_node.arguments.last.source_range
85
+
86
+ corrector.insert_after(range, new_rel_exp)
87
+ end
88
+
89
+ def contains_noopener?(value)
90
+ return false unless value
91
+
92
+ rel_array = value.to_s.split(' ')
93
+ rel_array.include?('noopener') || rel_array.include?('noreferrer')
94
+ end
95
+ end
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
6
+ # This cop checks for add_column call with NOT NULL constraint
7
+ # in migration file.
8
+ #
9
+ # @example
10
+ # # bad
11
+ # add_column :users, :name, :string, null: false
12
+ # add_reference :products, :category, null: false
13
+ #
14
+ # # good
15
+ # add_column :users, :name, :string, null: true
16
+ # add_column :users, :name, :string, null: false, default: ''
17
+ # add_reference :products, :category
18
+ # add_reference :products, :category, null: false, default: 1
19
+ class NotNullColumn < Cop
20
+ MSG = 'Do not add a NOT NULL column without a default value.'
21
+
22
+ def_node_matcher :add_not_null_column?, <<-PATTERN
23
+ (send nil? :add_column _ _ _ (hash $...))
24
+ PATTERN
25
+
26
+ def_node_matcher :add_not_null_reference?, <<-PATTERN
27
+ (send nil? :add_reference _ _ (hash $...))
28
+ PATTERN
29
+
30
+ def_node_matcher :null_false?, <<-PATTERN
31
+ (pair (sym :null) (false))
32
+ PATTERN
33
+
34
+ def_node_matcher :default_option?, <<-PATTERN
35
+ (pair (sym :default) !nil)
36
+ PATTERN
37
+
38
+ def on_send(node)
39
+ check_add_column(node)
40
+ check_add_reference(node)
41
+ end
42
+
43
+ private
44
+
45
+ def check_add_column(node)
46
+ pairs = add_not_null_column?(node)
47
+ check_pairs(pairs)
48
+ end
49
+
50
+ def check_add_reference(node)
51
+ pairs = add_not_null_reference?(node)
52
+ check_pairs(pairs)
53
+ end
54
+
55
+ def check_pairs(pairs)
56
+ return unless pairs
57
+ return if pairs.any? { |pair| default_option?(pair) }
58
+
59
+ null_false = pairs.find { |pair| null_false?(pair) }
60
+ return unless null_false
61
+
62
+ add_offense(null_false)
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
6
+ # This cop checks for the use of output calls like puts and print
7
+ #
8
+ # @example
9
+ # # bad
10
+ # puts 'A debug message'
11
+ # pp 'A debug message'
12
+ # print 'A debug message'
13
+ #
14
+ # # good
15
+ # Rails.logger.debug 'A debug message'
16
+ class Output < Cop
17
+ MSG = 'Do not write to stdout. ' \
18
+ "Use Rails's logger if you want to log."
19
+
20
+ def_node_matcher :output?, <<-PATTERN
21
+ (send nil? {:ap :p :pp :pretty_print :print :puts} ...)
22
+ PATTERN
23
+
24
+ def_node_matcher :io_output?, <<-PATTERN
25
+ (send
26
+ {
27
+ (gvar #match_gvar?)
28
+ {(const nil? :STDOUT) (const nil? :STDERR)}
29
+ }
30
+ {:binwrite :syswrite :write :write_nonblock}
31
+ ...)
32
+ PATTERN
33
+
34
+ def on_send(node)
35
+ return unless (output?(node) || io_output?(node)) &&
36
+ node.arguments?
37
+
38
+ add_offense(node, location: :selector)
39
+ end
40
+
41
+ private
42
+
43
+ def match_gvar?(sym)
44
+ %i[$stdout $stderr].include?(sym)
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,99 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
6
+ # This cop checks for the use of output safety calls like `html_safe`,
7
+ # `raw`, and `safe_concat`. These methods do not escape content. They
8
+ # simply return a SafeBuffer containing the content as is. Instead,
9
+ # use `safe_join` to join content and escape it and concat to
10
+ # concatenate content and escape it, ensuring its safety.
11
+ #
12
+ # @example
13
+ # user_content = "<b>hi</b>"
14
+ #
15
+ # # bad
16
+ # "<p>#{user_content}</p>".html_safe
17
+ # # => ActiveSupport::SafeBuffer "<p><b>hi</b></p>"
18
+ #
19
+ # # good
20
+ # content_tag(:p, user_content)
21
+ # # => ActiveSupport::SafeBuffer "<p>&lt;b&gt;hi&lt;/b&gt;</p>"
22
+ #
23
+ # # bad
24
+ # out = ""
25
+ # out << "<li>#{user_content}</li>"
26
+ # out << "<li>#{user_content}</li>"
27
+ # out.html_safe
28
+ # # => ActiveSupport::SafeBuffer "<li><b>hi</b></li><li><b>hi</b></li>"
29
+ #
30
+ # # good
31
+ # out = []
32
+ # out << content_tag(:li, user_content)
33
+ # out << content_tag(:li, user_content)
34
+ # safe_join(out)
35
+ # # => ActiveSupport::SafeBuffer
36
+ # # "<li>&lt;b&gt;hi&lt;/b&gt;</li><li>&lt;b&gt;hi&lt;/b&gt;</li>"
37
+ #
38
+ # # bad
39
+ # out = "<h1>trusted content</h1>".html_safe
40
+ # out.safe_concat(user_content)
41
+ # # => ActiveSupport::SafeBuffer "<h1>trusted_content</h1><b>hi</b>"
42
+ #
43
+ # # good
44
+ # out = "<h1>trusted content</h1>".html_safe
45
+ # out.concat(user_content)
46
+ # # => ActiveSupport::SafeBuffer
47
+ # # "<h1>trusted_content</h1>&lt;b&gt;hi&lt;/b&gt;"
48
+ #
49
+ # # safe, though maybe not good style
50
+ # out = "trusted content"
51
+ # result = out.concat(user_content)
52
+ # # => String "trusted content<b>hi</b>"
53
+ # # because when rendered in ERB the String will be escaped:
54
+ # # <%= result %>
55
+ # # => trusted content&lt;b&gt;hi&lt;/b&gt;
56
+ #
57
+ # # bad
58
+ # (user_content + " " + content_tag(:span, user_content)).html_safe
59
+ # # => ActiveSupport::SafeBuffer "<b>hi</b> <span><b>hi</b></span>"
60
+ #
61
+ # # good
62
+ # safe_join([user_content, " ", content_tag(:span, user_content)])
63
+ # # => ActiveSupport::SafeBuffer
64
+ # # "&lt;b&gt;hi&lt;/b&gt; <span>&lt;b&gt;hi&lt;/b&gt;</span>"
65
+ class OutputSafety < Cop
66
+ MSG = 'Tagging a string as html safe may be a security risk.'
67
+
68
+ def on_send(node)
69
+ return if non_interpolated_string?(node)
70
+
71
+ return unless looks_like_rails_html_safe?(node) ||
72
+ looks_like_rails_raw?(node) ||
73
+ looks_like_rails_safe_concat?(node)
74
+
75
+ add_offense(node, location: :selector)
76
+ end
77
+ alias on_csend on_send
78
+
79
+ private
80
+
81
+ def non_interpolated_string?(node)
82
+ node.receiver&.str_type? && !node.receiver.dstr_type?
83
+ end
84
+
85
+ def looks_like_rails_html_safe?(node)
86
+ node.receiver && node.method?(:html_safe) && !node.arguments?
87
+ end
88
+
89
+ def looks_like_rails_raw?(node)
90
+ node.command?(:raw) && node.arguments.one?
91
+ end
92
+
93
+ def looks_like_rails_safe_concat?(node)
94
+ node.method?(:safe_concat) && node.arguments.one?
95
+ end
96
+ end
97
+ end
98
+ end
99
+ end