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,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
@@ -0,0 +1,107 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
6
+ # This cop checks for correct grammar when using ActiveSupport's
7
+ # core extensions to the numeric classes.
8
+ #
9
+ # @example
10
+ # # bad
11
+ # 3.day.ago
12
+ # 1.months.ago
13
+ #
14
+ # # good
15
+ # 3.days.ago
16
+ # 1.month.ago
17
+ class PluralizationGrammar < Cop
18
+ SINGULAR_DURATION_METHODS = { second: :seconds,
19
+ minute: :minutes,
20
+ hour: :hours,
21
+ day: :days,
22
+ week: :weeks,
23
+ fortnight: :fortnights,
24
+ month: :months,
25
+ year: :years }.freeze
26
+
27
+ PLURAL_DURATION_METHODS = SINGULAR_DURATION_METHODS.invert.freeze
28
+
29
+ MSG = 'Prefer `%<number>s.%<correct>s`.'
30
+
31
+ def on_send(node)
32
+ return unless duration_method?(node.method_name)
33
+ return unless literal_number?(node.receiver)
34
+
35
+ return unless offense?(node)
36
+
37
+ add_offense(node)
38
+ end
39
+
40
+ def autocorrect(node)
41
+ lambda do |corrector|
42
+ method_name = node.loc.selector.source
43
+
44
+ corrector.replace(node.loc.selector, correct_method(method_name))
45
+ end
46
+ end
47
+
48
+ private
49
+
50
+ def message(node)
51
+ number, = *node.receiver
52
+
53
+ format(MSG, number: number,
54
+ correct: correct_method(node.method_name.to_s))
55
+ end
56
+
57
+ def correct_method(method_name)
58
+ if plural_method?(method_name)
59
+ singularize(method_name)
60
+ else
61
+ pluralize(method_name)
62
+ end
63
+ end
64
+
65
+ def offense?(node)
66
+ number, = *node.receiver
67
+
68
+ singular_receiver?(number) && plural_method?(node.method_name) ||
69
+ plural_receiver?(number) && singular_method?(node.method_name)
70
+ end
71
+
72
+ def plural_method?(method_name)
73
+ method_name.to_s.end_with?('s')
74
+ end
75
+
76
+ def singular_method?(method_name)
77
+ !plural_method?(method_name)
78
+ end
79
+
80
+ def singular_receiver?(number)
81
+ number.abs == 1
82
+ end
83
+
84
+ def plural_receiver?(number)
85
+ !singular_receiver?(number)
86
+ end
87
+
88
+ def literal_number?(node)
89
+ node && (node.int_type? || node.float_type?)
90
+ end
91
+
92
+ def pluralize(method_name)
93
+ SINGULAR_DURATION_METHODS.fetch(method_name.to_sym).to_s
94
+ end
95
+
96
+ def singularize(method_name)
97
+ PLURAL_DURATION_METHODS.fetch(method_name.to_sym).to_s
98
+ end
99
+
100
+ def duration_method?(method_name)
101
+ SINGULAR_DURATION_METHODS.key?(method_name) ||
102
+ PLURAL_DURATION_METHODS.key?(method_name)
103
+ end
104
+ end
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,148 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
6
+ # This cop checks code that can be written more easily using
7
+ # `Object#presence` defined by Active Support.
8
+ #
9
+ # @example
10
+ # # bad
11
+ # a.present? ? a : nil
12
+ #
13
+ # # bad
14
+ # !a.present? ? nil : a
15
+ #
16
+ # # bad
17
+ # a.blank? ? nil : a
18
+ #
19
+ # # bad
20
+ # !a.blank? ? a : nil
21
+ #
22
+ # # good
23
+ # a.presence
24
+ #
25
+ # @example
26
+ # # bad
27
+ # a.present? ? a : b
28
+ #
29
+ # # bad
30
+ # !a.present? ? b : a
31
+ #
32
+ # # bad
33
+ # a.blank? ? b : a
34
+ #
35
+ # # bad
36
+ # !a.blank? ? a : b
37
+ #
38
+ # # good
39
+ # a.presence || b
40
+ class Presence < Cop
41
+ include RangeHelp
42
+
43
+ MSG = 'Use `%<prefer>s` instead of `%<current>s`.'
44
+
45
+ def_node_matcher :redundant_receiver_and_other, <<~PATTERN
46
+ {
47
+ (if
48
+ (send $_recv :present?)
49
+ _recv
50
+ $!begin
51
+ )
52
+ (if
53
+ (send $_recv :blank?)
54
+ $!begin
55
+ _recv
56
+ )
57
+ }
58
+ PATTERN
59
+
60
+ def_node_matcher :redundant_negative_receiver_and_other, <<~PATTERN
61
+ {
62
+ (if
63
+ (send (send $_recv :present?) :!)
64
+ $!begin
65
+ _recv
66
+ )
67
+ (if
68
+ (send (send $_recv :blank?) :!)
69
+ _recv
70
+ $!begin
71
+ )
72
+ }
73
+ PATTERN
74
+
75
+ def on_if(node)
76
+ return if ignore_if_node?(node)
77
+
78
+ redundant_receiver_and_other(node) do |receiver, other|
79
+ unless ignore_other_node?(other) || receiver.nil?
80
+ add_offense(node, message: message(node, receiver, other))
81
+ end
82
+ end
83
+
84
+ redundant_negative_receiver_and_other(node) do |receiver, other|
85
+ unless ignore_other_node?(other) || receiver.nil?
86
+ add_offense(node, message: message(node, receiver, other))
87
+ end
88
+ end
89
+ end
90
+
91
+ def autocorrect(node)
92
+ lambda do |corrector|
93
+ redundant_receiver_and_other(node) do |receiver, other|
94
+ corrector.replace(node.source_range, replacement(receiver, other))
95
+ end
96
+
97
+ redundant_negative_receiver_and_other(node) do |receiver, other|
98
+ corrector.replace(node.source_range, replacement(receiver, other))
99
+ end
100
+ end
101
+ end
102
+
103
+ private
104
+
105
+ def ignore_if_node?(node)
106
+ node.elsif?
107
+ end
108
+
109
+ def ignore_other_node?(node)
110
+ node && (node.if_type? || node.rescue_type? || node.while_type?)
111
+ end
112
+
113
+ def message(node, receiver, other)
114
+ format(MSG,
115
+ prefer: replacement(receiver, other),
116
+ current: node.source)
117
+ end
118
+
119
+ def replacement(receiver, other)
120
+ or_source = if other&.send_type?
121
+ build_source_for_or_method(other)
122
+ elsif other.nil? || other.nil_type?
123
+ ''
124
+ else
125
+ " || #{other.source}"
126
+ end
127
+
128
+ "#{receiver.source}.presence" + or_source
129
+ end
130
+
131
+ def build_source_for_or_method(other)
132
+ if other.parenthesized? || other.method?('[]') || !other.arguments?
133
+ " || #{other.source}"
134
+ else
135
+ method = range_between(
136
+ other.source_range.begin_pos,
137
+ other.first_argument.source_range.begin_pos - 1
138
+ ).source
139
+
140
+ arguments = other.arguments.map(&:source).join(', ')
141
+
142
+ " || #{method}(#{arguments})"
143
+ end
144
+ end
145
+ end
146
+ end
147
+ end
148
+ end