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