rubocop-rails 2.4.2 → 2.7.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 (53) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.txt +1 -1
  3. data/README.md +5 -1
  4. data/config/default.yml +179 -9
  5. data/lib/rubocop-rails.rb +3 -0
  6. data/lib/rubocop/cop/mixin/active_record_helper.rb +84 -0
  7. data/lib/rubocop/cop/mixin/index_method.rb +161 -0
  8. data/lib/rubocop/cop/rails/active_record_callbacks_order.rb +145 -0
  9. data/lib/rubocop/cop/rails/content_tag.rb +69 -0
  10. data/lib/rubocop/cop/rails/create_table_with_timestamps.rb +1 -3
  11. data/lib/rubocop/cop/rails/default_scope.rb +54 -0
  12. data/lib/rubocop/cop/rails/delegate.rb +2 -4
  13. data/lib/rubocop/cop/rails/dynamic_find_by.rb +40 -15
  14. data/lib/rubocop/cop/rails/environment_comparison.rb +60 -14
  15. data/lib/rubocop/cop/rails/exit.rb +2 -2
  16. data/lib/rubocop/cop/rails/file_path.rb +2 -1
  17. data/lib/rubocop/cop/rails/find_by_id.rb +103 -0
  18. data/lib/rubocop/cop/rails/http_positional_arguments.rb +2 -2
  19. data/lib/rubocop/cop/rails/http_status.rb +2 -0
  20. data/lib/rubocop/cop/rails/index_by.rb +56 -0
  21. data/lib/rubocop/cop/rails/index_with.rb +59 -0
  22. data/lib/rubocop/cop/rails/inquiry.rb +34 -0
  23. data/lib/rubocop/cop/rails/inverse_of.rb +0 -4
  24. data/lib/rubocop/cop/rails/link_to_blank.rb +3 -3
  25. data/lib/rubocop/cop/rails/mailer_name.rb +80 -0
  26. data/lib/rubocop/cop/rails/match_route.rb +117 -0
  27. data/lib/rubocop/cop/rails/negate_include.rb +39 -0
  28. data/lib/rubocop/cop/rails/pick.rb +55 -0
  29. data/lib/rubocop/cop/rails/pluck.rb +59 -0
  30. data/lib/rubocop/cop/rails/pluck_id.rb +58 -0
  31. data/lib/rubocop/cop/rails/pluck_in_where.rb +36 -0
  32. data/lib/rubocop/cop/rails/presence.rb +2 -6
  33. data/lib/rubocop/cop/rails/rake_environment.rb +17 -0
  34. data/lib/rubocop/cop/rails/redundant_foreign_key.rb +80 -0
  35. data/lib/rubocop/cop/rails/redundant_receiver_in_with_options.rb +0 -3
  36. data/lib/rubocop/cop/rails/refute_methods.rb +52 -26
  37. data/lib/rubocop/cop/rails/render_inline.rb +48 -0
  38. data/lib/rubocop/cop/rails/render_plain_text.rb +76 -0
  39. data/lib/rubocop/cop/rails/reversible_migration.rb +6 -1
  40. data/lib/rubocop/cop/rails/safe_navigation.rb +1 -1
  41. data/lib/rubocop/cop/rails/save_bang.rb +6 -7
  42. data/lib/rubocop/cop/rails/short_i18n.rb +76 -0
  43. data/lib/rubocop/cop/rails/skips_model_validations.rb +46 -8
  44. data/lib/rubocop/cop/rails/time_zone.rb +1 -3
  45. data/lib/rubocop/cop/rails/uniq_before_pluck.rb +12 -12
  46. data/lib/rubocop/cop/rails/unique_validation_without_index.rb +155 -0
  47. data/lib/rubocop/cop/rails/unknown_env.rb +7 -6
  48. data/lib/rubocop/cop/rails/where_exists.rb +68 -0
  49. data/lib/rubocop/cop/rails_cops.rb +22 -0
  50. data/lib/rubocop/rails/schema_loader.rb +61 -0
  51. data/lib/rubocop/rails/schema_loader/schema.rb +190 -0
  52. data/lib/rubocop/rails/version.rb +1 -1
  53. metadata +46 -8
@@ -0,0 +1,161 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ # Common functionality for Rails/IndexBy and Rails/IndexWith
6
+ module IndexMethod # rubocop:disable Metrics/ModuleLength
7
+ def on_block(node)
8
+ on_bad_each_with_object(node) do |*match|
9
+ handle_possible_offense(node, match, 'each_with_object')
10
+ end
11
+ end
12
+
13
+ def on_send(node)
14
+ on_bad_map_to_h(node) do |*match|
15
+ handle_possible_offense(node, match, 'map { ... }.to_h')
16
+ end
17
+
18
+ on_bad_hash_brackets_map(node) do |*match|
19
+ handle_possible_offense(node, match, 'Hash[map { ... }]')
20
+ end
21
+ end
22
+
23
+ def on_csend(node)
24
+ on_bad_map_to_h(node) do |*match|
25
+ handle_possible_offense(node, match, 'map { ... }.to_h')
26
+ end
27
+ end
28
+
29
+ def autocorrect(node)
30
+ lambda do |corrector|
31
+ correction = prepare_correction(node)
32
+ execute_correction(corrector, node, correction)
33
+ end
34
+ end
35
+
36
+ private
37
+
38
+ # @abstract Implemented with `def_node_matcher`
39
+ def on_bad_each_with_object(_node)
40
+ raise NotImplementedError
41
+ end
42
+
43
+ # @abstract Implemented with `def_node_matcher`
44
+ def on_bad_map_to_h(_node)
45
+ raise NotImplementedError
46
+ end
47
+
48
+ # @abstract Implemented with `def_node_matcher`
49
+ def on_bad_hash_brackets_map(_node)
50
+ raise NotImplementedError
51
+ end
52
+
53
+ def handle_possible_offense(node, match, match_desc)
54
+ captures = extract_captures(match)
55
+
56
+ return if captures.noop_transformation?
57
+
58
+ add_offense(
59
+ node,
60
+ message: "Prefer `#{new_method_name}` over `#{match_desc}`."
61
+ )
62
+ end
63
+
64
+ def extract_captures(match)
65
+ argname, body_expr = *match
66
+ Captures.new(argname, body_expr)
67
+ end
68
+
69
+ def new_method_name
70
+ raise NotImplementedError
71
+ end
72
+
73
+ def prepare_correction(node)
74
+ if (match = on_bad_each_with_object(node))
75
+ Autocorrection.from_each_with_object(node, match)
76
+ elsif (match = on_bad_map_to_h(node))
77
+ Autocorrection.from_map_to_h(node, match)
78
+ elsif (match = on_bad_hash_brackets_map(node))
79
+ Autocorrection.from_hash_brackets_map(node, match)
80
+ else
81
+ raise 'unreachable'
82
+ end
83
+ end
84
+
85
+ def execute_correction(corrector, node, correction)
86
+ correction.strip_prefix_and_suffix(node, corrector)
87
+ correction.set_new_method_name(new_method_name, corrector)
88
+
89
+ captures = extract_captures(correction.match)
90
+ correction.set_new_arg_name(captures.transformed_argname, corrector)
91
+ correction.set_new_body_expression(
92
+ captures.transforming_body_expr,
93
+ corrector
94
+ )
95
+ end
96
+
97
+ # Internal helper class to hold match data
98
+ Captures = Struct.new(
99
+ :transformed_argname,
100
+ :transforming_body_expr
101
+ ) do
102
+ def noop_transformation?
103
+ transforming_body_expr.lvar_type? &&
104
+ transforming_body_expr.children == [transformed_argname]
105
+ end
106
+ end
107
+
108
+ # Internal helper class to hold autocorrect data
109
+ Autocorrection = Struct.new(:match, :block_node, :leading, :trailing) do # rubocop:disable Metrics/BlockLength
110
+ def self.from_each_with_object(node, match)
111
+ new(match, node, 0, 0)
112
+ end
113
+
114
+ def self.from_map_to_h(node, match)
115
+ strip_trailing_chars = 0
116
+
117
+ unless node.parent&.block_type?
118
+ map_range = node.children.first.source_range
119
+ node_range = node.source_range
120
+ strip_trailing_chars = node_range.end_pos - map_range.end_pos
121
+ end
122
+
123
+ new(match, node.children.first, 0, strip_trailing_chars)
124
+ end
125
+
126
+ def self.from_hash_brackets_map(node, match)
127
+ new(match, node.children.last, 'Hash['.length, ']'.length)
128
+ end
129
+
130
+ def strip_prefix_and_suffix(node, corrector)
131
+ expression = node.loc.expression
132
+ corrector.remove_leading(expression, leading)
133
+ corrector.remove_trailing(expression, trailing)
134
+ end
135
+
136
+ def set_new_method_name(new_method_name, corrector)
137
+ range = block_node.send_node.loc.selector
138
+ if (send_end = block_node.send_node.loc.end)
139
+ # If there are arguments (only true in the `each_with_object` case)
140
+ range = range.begin.join(send_end)
141
+ end
142
+ corrector.replace(range, new_method_name)
143
+ end
144
+
145
+ def set_new_arg_name(transformed_argname, corrector)
146
+ corrector.replace(
147
+ block_node.arguments.loc.expression,
148
+ "|#{transformed_argname}|"
149
+ )
150
+ end
151
+
152
+ def set_new_body_expression(transforming_body_expr, corrector)
153
+ corrector.replace(
154
+ block_node.body.loc.expression,
155
+ transforming_body_expr.loc.expression.source
156
+ )
157
+ end
158
+ end
159
+ end
160
+ end
161
+ end
@@ -0,0 +1,145 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
6
+ # This cop checks that Active Record callbacks are declared
7
+ # in the order in which they will be executed.
8
+ #
9
+ # @example
10
+ # # bad
11
+ # class Person < ApplicationRecord
12
+ # after_commit :after_commit_callback
13
+ # before_validation :before_validation_callback
14
+ # end
15
+ #
16
+ # # good
17
+ # class Person < ApplicationRecord
18
+ # before_validation :before_validation_callback
19
+ # after_commit :after_commit_callback
20
+ # end
21
+ #
22
+ class ActiveRecordCallbacksOrder < Cop
23
+ MSG = '`%<current>s` is supposed to appear before `%<previous>s`.'
24
+
25
+ CALLBACKS_IN_ORDER = %i[
26
+ after_initialize
27
+ before_validation
28
+ after_validation
29
+ before_save
30
+ around_save
31
+ before_create
32
+ around_create
33
+ after_create
34
+ before_update
35
+ around_update
36
+ after_update
37
+ before_destroy
38
+ around_destroy
39
+ after_destroy
40
+ after_save
41
+ after_commit
42
+ after_rollback
43
+ after_find
44
+ after_touch
45
+ ].freeze
46
+
47
+ CALLBACKS_ORDER_MAP = Hash[
48
+ CALLBACKS_IN_ORDER.map.with_index { |name, index| [name, index] }
49
+ ].freeze
50
+
51
+ def on_class(class_node)
52
+ previous_index = -1
53
+ previous_callback = nil
54
+
55
+ defined_callbacks(class_node).each do |node|
56
+ callback = node.method_name
57
+ index = CALLBACKS_ORDER_MAP[callback]
58
+
59
+ if index < previous_index
60
+ message = format(MSG, current: callback,
61
+ previous: previous_callback)
62
+ add_offense(node, message: message)
63
+ end
64
+ previous_index = index
65
+ previous_callback = callback
66
+ end
67
+ end
68
+
69
+ # Autocorrect by swapping between two nodes autocorrecting them
70
+ def autocorrect(node)
71
+ previous = left_siblings_of(node).find do |sibling|
72
+ callback?(sibling)
73
+ end
74
+
75
+ current_range = source_range_with_comment(node)
76
+ previous_range = source_range_with_comment(previous)
77
+
78
+ lambda do |corrector|
79
+ corrector.insert_before(previous_range, current_range.source)
80
+ corrector.remove(current_range)
81
+ end
82
+ end
83
+
84
+ private
85
+
86
+ def defined_callbacks(class_node)
87
+ class_def = class_node.body
88
+
89
+ if class_def
90
+ class_def.each_child_node.select { |c| callback?(c) }
91
+ else
92
+ []
93
+ end
94
+ end
95
+
96
+ def callback?(node)
97
+ node.send_type? && CALLBACKS_ORDER_MAP.key?(node.method_name)
98
+ end
99
+
100
+ def left_siblings_of(node)
101
+ siblings_of(node)[0, node.sibling_index]
102
+ end
103
+
104
+ def siblings_of(node)
105
+ node.parent.children
106
+ end
107
+
108
+ def source_range_with_comment(node)
109
+ begin_pos = begin_pos_with_comment(node)
110
+ end_pos = end_position_for(node)
111
+
112
+ Parser::Source::Range.new(buffer, begin_pos, end_pos)
113
+ end
114
+
115
+ def end_position_for(node)
116
+ end_line = buffer.line_for_position(node.loc.expression.end_pos)
117
+ buffer.line_range(end_line).end_pos
118
+ end
119
+
120
+ def begin_pos_with_comment(node)
121
+ annotation_line = node.first_line - 1
122
+ first_comment = nil
123
+
124
+ processed_source.comments_before_line(annotation_line)
125
+ .reverse_each do |comment|
126
+ if comment.location.line == annotation_line
127
+ first_comment = comment
128
+ annotation_line -= 1
129
+ end
130
+ end
131
+
132
+ start_line_position(first_comment || node)
133
+ end
134
+
135
+ def start_line_position(node)
136
+ buffer.line_range(node.loc.line).begin_pos - 1
137
+ end
138
+
139
+ def buffer
140
+ processed_source.buffer
141
+ end
142
+ end
143
+ end
144
+ end
145
+ end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
6
+ # This cop checks that `tag` is used instead of `content_tag`
7
+ # because `content_tag` is legacy syntax.
8
+ #
9
+ # NOTE: Allow `content_tag` when the first argument is a variable because
10
+ # `content_tag(name)` is simpler rather than `tag.public_send(name)`.
11
+ #
12
+ # @example
13
+ # # bad
14
+ # content_tag(:p, 'Hello world!')
15
+ # content_tag(:br)
16
+ #
17
+ # # good
18
+ # tag.p('Hello world!')
19
+ # tag.br
20
+ # content_tag(name, 'Hello world!')
21
+ class ContentTag < Cop
22
+ include RangeHelp
23
+ extend TargetRailsVersion
24
+
25
+ minimum_target_rails_version 5.1
26
+
27
+ MSG = 'Use `tag` instead of `content_tag`.'
28
+
29
+ def on_send(node)
30
+ return unless node.method?(:content_tag)
31
+
32
+ first_argument = node.first_argument
33
+ return unless first_argument
34
+
35
+ return if first_argument.variable? || first_argument.send_type? || first_argument.const_type?
36
+
37
+ add_offense(node)
38
+ end
39
+
40
+ def autocorrect(node)
41
+ lambda do |corrector|
42
+ if method_name?(node.first_argument)
43
+ range = correction_range(node)
44
+
45
+ rest_args = node.arguments.drop(1)
46
+ replacement = "tag.#{node.first_argument.value}(#{rest_args.map(&:source).join(', ')})"
47
+
48
+ corrector.replace(range, replacement)
49
+ else
50
+ corrector.replace(node.loc.selector, 'tag')
51
+ end
52
+ end
53
+ end
54
+
55
+ private
56
+
57
+ def method_name?(node)
58
+ return false unless node.str_type? || node.sym_type?
59
+
60
+ /^[a-zA-Z_][a-zA-Z_0-9]*$/.match?(node.value)
61
+ end
62
+
63
+ def correction_range(node)
64
+ range_between(node.loc.selector.begin_pos, node.loc.expression.end_pos)
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
@@ -70,9 +70,7 @@ module RuboCop
70
70
  parent = node.parent
71
71
 
72
72
  if create_table_with_block?(parent)
73
- if parent.body.nil? || !time_columns_included?(parent.body)
74
- add_offense(parent)
75
- end
73
+ add_offense(parent) if parent.body.nil? || !time_columns_included?(parent.body)
76
74
  elsif create_table_with_timestamps_proc?(node)
77
75
  # nothing to do
78
76
  else
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
6
+ # This cop looks for uses of `default_scope`.
7
+ #
8
+ # @example
9
+ # # bad
10
+ # default_scope -> { where(hidden: false) }
11
+ #
12
+ # # good
13
+ # scope :published, -> { where(hidden: false) }
14
+ #
15
+ # # bad
16
+ # def self.default_scope
17
+ # where(hidden: false)
18
+ # end
19
+ #
20
+ # # good
21
+ # def self.published
22
+ # where(hidden: false)
23
+ # end
24
+ #
25
+ class DefaultScope < Cop
26
+ MSG = 'Avoid use of `default_scope`. It is better to use explicitly named scopes.'
27
+
28
+ def_node_matcher :method_call?, <<~PATTERN
29
+ (send nil? :default_scope ...)
30
+ PATTERN
31
+
32
+ def_node_matcher :class_method_definition?, <<~PATTERN
33
+ (defs _ :default_scope args ...)
34
+ PATTERN
35
+
36
+ def_node_matcher :eigenclass_method_definition?, <<~PATTERN
37
+ (sclass (self) $(def :default_scope args ...))
38
+ PATTERN
39
+
40
+ def on_send(node)
41
+ add_offense(node, location: :selector) if method_call?(node)
42
+ end
43
+
44
+ def on_defs(node)
45
+ add_offense(node, location: :name) if class_method_definition?(node)
46
+ end
47
+
48
+ def on_sclass(node)
49
+ eigenclass_method_definition?(node) { |default_scope| add_offense(default_scope, location: :name) }
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -71,9 +71,7 @@ module RuboCop
71
71
  delegation = ["delegate :#{node.body.method_name}",
72
72
  "to: :#{node.body.receiver.method_name}"]
73
73
 
74
- if node.method?(prefixed_method_name(node.body))
75
- delegation << ['prefix: true']
76
- end
74
+ delegation << ['prefix: true'] if node.method?(prefixed_method_name(node.body))
77
75
 
78
76
  lambda do |corrector|
79
77
  corrector.replace(node.source_range, delegation.join(', '))
@@ -124,7 +122,7 @@ module RuboCop
124
122
  end
125
123
 
126
124
  def private_or_protected_inline(line)
127
- processed_source[line - 1].strip =~ /\A(private )|(protected )/
125
+ processed_source[line - 1].strip.match?(/\A(private )|(protected )/)
128
126
  end
129
127
  end
130
128
  end