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