rubocop-rails 2.5.0 → 2.7.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 (47) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.txt +1 -1
  3. data/README.md +2 -2
  4. data/config/default.yml +156 -9
  5. data/lib/rubocop/cop/mixin/active_record_helper.rb +22 -0
  6. data/lib/rubocop/cop/mixin/index_method.rb +8 -1
  7. data/lib/rubocop/cop/rails/active_record_callbacks_order.rb +145 -0
  8. data/lib/rubocop/cop/rails/content_tag.rb +69 -0
  9. data/lib/rubocop/cop/rails/create_table_with_timestamps.rb +1 -3
  10. data/lib/rubocop/cop/rails/default_scope.rb +54 -0
  11. data/lib/rubocop/cop/rails/delegate.rb +2 -4
  12. data/lib/rubocop/cop/rails/dynamic_find_by.rb +40 -15
  13. data/lib/rubocop/cop/rails/exit.rb +2 -2
  14. data/lib/rubocop/cop/rails/file_path.rb +2 -1
  15. data/lib/rubocop/cop/rails/find_by_id.rb +103 -0
  16. data/lib/rubocop/cop/rails/http_positional_arguments.rb +1 -1
  17. data/lib/rubocop/cop/rails/http_status.rb +2 -0
  18. data/lib/rubocop/cop/rails/inquiry.rb +34 -0
  19. data/lib/rubocop/cop/rails/inverse_of.rb +0 -4
  20. data/lib/rubocop/cop/rails/link_to_blank.rb +3 -3
  21. data/lib/rubocop/cop/rails/mailer_name.rb +80 -0
  22. data/lib/rubocop/cop/rails/match_route.rb +119 -0
  23. data/lib/rubocop/cop/rails/negate_include.rb +39 -0
  24. data/lib/rubocop/cop/rails/pick.rb +55 -0
  25. data/lib/rubocop/cop/rails/pluck.rb +59 -0
  26. data/lib/rubocop/cop/rails/pluck_id.rb +58 -0
  27. data/lib/rubocop/cop/rails/pluck_in_where.rb +36 -0
  28. data/lib/rubocop/cop/rails/presence.rb +2 -6
  29. data/lib/rubocop/cop/rails/rake_environment.rb +17 -0
  30. data/lib/rubocop/cop/rails/redundant_foreign_key.rb +80 -0
  31. data/lib/rubocop/cop/rails/redundant_receiver_in_with_options.rb +0 -3
  32. data/lib/rubocop/cop/rails/render_inline.rb +40 -0
  33. data/lib/rubocop/cop/rails/render_plain_text.rb +76 -0
  34. data/lib/rubocop/cop/rails/safe_navigation.rb +1 -1
  35. data/lib/rubocop/cop/rails/save_bang.rb +6 -7
  36. data/lib/rubocop/cop/rails/short_i18n.rb +76 -0
  37. data/lib/rubocop/cop/rails/skips_model_validations.rb +46 -8
  38. data/lib/rubocop/cop/rails/time_zone.rb +1 -3
  39. data/lib/rubocop/cop/rails/uniq_before_pluck.rb +12 -12
  40. data/lib/rubocop/cop/rails/unique_validation_without_index.rb +28 -6
  41. data/lib/rubocop/cop/rails/unknown_env.rb +18 -6
  42. data/lib/rubocop/cop/rails/where_exists.rb +68 -0
  43. data/lib/rubocop/cop/rails_cops.rb +17 -0
  44. data/lib/rubocop/rails/schema_loader.rb +10 -10
  45. data/lib/rubocop/rails/schema_loader/schema.rb +48 -14
  46. data/lib/rubocop/rails/version.rb +1 -1
  47. metadata +27 -10
@@ -85,9 +85,7 @@ module RuboCop
85
85
  end
86
86
 
87
87
  # prefer `Time` over `DateTime` class
88
- if strict?
89
- corrector.replace(node.children.first.source_range, 'Time')
90
- end
88
+ corrector.replace(node.children.first.source_range, 'Time') if strict?
91
89
  remove_redundant_in_time_zone(corrector, node)
92
90
  end
93
91
  end
@@ -3,19 +3,20 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Rails
6
- # Prefer the use of uniq (or distinct), before pluck instead of after.
6
+ # Prefer the use of distinct, before pluck instead of after.
7
7
  #
8
- # The use of uniq before pluck is preferred because it executes within
8
+ # The use of distinct before pluck is preferred because it executes within
9
9
  # the database.
10
10
  #
11
11
  # This cop has two different enforcement modes. When the EnforcedStyle
12
12
  # is conservative (the default) then only calls to pluck on a constant
13
- # (i.e. a model class) before uniq are added as offenses.
13
+ # (i.e. a model class) before distinct are added as offenses.
14
14
  #
15
15
  # When the EnforcedStyle is aggressive then all calls to pluck before
16
- # uniq are added as offenses. This may lead to false positives as the cop
17
- # cannot distinguish between calls to pluck on an ActiveRecord::Relation
18
- # vs a call to pluck on an ActiveRecord::Associations::CollectionProxy.
16
+ # distinct are added as offenses. This may lead to false positives
17
+ # as the cop cannot distinguish between calls to pluck on an
18
+ # ActiveRecord::Relation vs a call to pluck on an
19
+ # ActiveRecord::Associations::CollectionProxy.
19
20
  #
20
21
  # Autocorrect is disabled by default for this cop since it may generate
21
22
  # false positives.
@@ -25,7 +26,7 @@ module RuboCop
25
26
  # Model.pluck(:id).uniq
26
27
  #
27
28
  # # good
28
- # Model.uniq.pluck(:id)
29
+ # Model.distinct.pluck(:id)
29
30
  #
30
31
  # @example EnforcedStyle: aggressive
31
32
  # # bad
@@ -40,13 +41,13 @@ module RuboCop
40
41
  # Model.pluck(:id).uniq
41
42
  #
42
43
  # # good
43
- # Model.uniq.pluck(:id)
44
+ # Model.distinct.pluck(:id)
44
45
  #
45
46
  class UniqBeforePluck < RuboCop::Cop::Cop
46
47
  include ConfigurableEnforcedStyle
47
48
  include RangeHelp
48
49
 
49
- MSG = 'Use `%<method>s` before `pluck`.'
50
+ MSG = 'Use `distinct` before `pluck`.'
50
51
  NEWLINE = "\n"
51
52
  PATTERN = '[!^block (send (send %<type>s :pluck ...) ' \
52
53
  '${:uniq :distinct} ...)]'
@@ -66,8 +67,7 @@ module RuboCop
66
67
 
67
68
  return unless method
68
69
 
69
- add_offense(node, location: :selector,
70
- message: format(MSG, method: method))
70
+ add_offense(node, location: :selector)
71
71
  end
72
72
 
73
73
  def autocorrect(node)
@@ -75,7 +75,7 @@ module RuboCop
75
75
  method = node.method_name
76
76
 
77
77
  corrector.remove(dot_method_with_whitespace(method, node))
78
- corrector.insert_before(node.receiver.loc.dot.begin, ".#{method}")
78
+ corrector.insert_before(node.receiver.loc.dot.begin, '.distinct')
79
79
  end
80
80
  end
81
81
 
@@ -32,6 +32,7 @@ module RuboCop
32
32
  def on_send(node)
33
33
  return unless node.method?(:validates)
34
34
  return unless uniqueness_part(node)
35
+ return if condition_part?(node)
35
36
  return unless schema
36
37
  return if with_index?(node)
37
38
 
@@ -50,8 +51,21 @@ module RuboCop
50
51
  names = column_names(node)
51
52
  return true unless names
52
53
 
53
- table.indices.any? do |index|
54
- index.unique && index.columns.to_set == names
54
+ # Compatibility for Rails 4.2.
55
+ add_indicies = schema.add_indicies_by(table_name: table_name(klass))
56
+
57
+ (table.indices + add_indicies).any? do |index|
58
+ index.unique &&
59
+ (index.columns.to_set == names ||
60
+ include_column_names_in_expression_index?(index, names))
61
+ end
62
+ end
63
+
64
+ def include_column_names_in_expression_index?(index, column_names)
65
+ return false unless (expression_index = index.expression)
66
+
67
+ column_names.all? do |column_name|
68
+ expression_index.include?(column_name)
55
69
  end
56
70
  end
57
71
 
@@ -113,6 +127,18 @@ module RuboCop
113
127
  end
114
128
  end
115
129
 
130
+ def condition_part?(node)
131
+ pairs = node.arguments.last
132
+ return unless pairs.hash_type?
133
+
134
+ pairs.each_pair.any? do |pair|
135
+ key = pair.key
136
+ next unless key.sym_type?
137
+
138
+ key.value == :if || key.value == :unless
139
+ end
140
+ end
141
+
116
142
  def array_node_to_array(node)
117
143
  node.values.map do |elm|
118
144
  case elm.type
@@ -123,10 +149,6 @@ module RuboCop
123
149
  end
124
150
  end
125
151
  end
126
-
127
- def schema
128
- RuboCop::Rails::SchemaLoader.load(target_ruby_version)
129
- end
130
152
  end
131
153
  end
132
154
  end
@@ -5,6 +5,9 @@ module RuboCop
5
5
  module Rails
6
6
  # This cop checks that environments called with `Rails.env` predicates
7
7
  # exist.
8
+ # By default the cop allows three environments which Rails ships with:
9
+ # `development`, `test`, and `production`.
10
+ # More can be added to the `Environments` config parameter.
8
11
  #
9
12
  # @example
10
13
  # # bad
@@ -15,8 +18,6 @@ module RuboCop
15
18
  # Rails.env.production?
16
19
  # Rails.env == 'production'
17
20
  class UnknownEnv < Cop
18
- include NameSimilarity
19
-
20
21
  MSG = 'Unknown environment `%<name>s`.'
21
22
  MSG_SIMILAR = 'Unknown environment `%<name>s`. ' \
22
23
  'Did you mean `%<similar>s`?'
@@ -57,11 +58,22 @@ module RuboCop
57
58
 
58
59
  def message(name)
59
60
  name = name.to_s.chomp('?')
60
- similar = find_similar_name(name, [])
61
- if similar
62
- format(MSG_SIMILAR, name: name, similar: similar)
63
- else
61
+
62
+ # DidYouMean::SpellChecker is not available in all versions of Ruby,
63
+ # and even on versions where it *is* available (>= 2.3), it is not
64
+ # always required correctly. So we do a feature check first. See:
65
+ # https://github.com/rubocop-hq/rubocop/issues/7979
66
+ similar_names = if defined?(DidYouMean::SpellChecker)
67
+ spell_checker = DidYouMean::SpellChecker.new(dictionary: environments)
68
+ spell_checker.correct(name)
69
+ else
70
+ []
71
+ end
72
+
73
+ if similar_names.empty?
64
74
  format(MSG, name: name)
75
+ else
76
+ format(MSG_SIMILAR, name: name, similar: similar_names.join(', '))
65
77
  end
66
78
  end
67
79
 
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
6
+ # This cop enforces the use of `exists?(...)` over `where(...).exists?`.
7
+ #
8
+ # @example
9
+ # # bad
10
+ # User.where(name: 'john').exists?
11
+ # User.where(['name = ?', 'john']).exists?
12
+ # User.where('name = ?', 'john').exists?
13
+ # user.posts.where(published: true).exists?
14
+ #
15
+ # # good
16
+ # User.exists?(name: 'john')
17
+ # User.where('length(name) > 10').exists?
18
+ # user.posts.exists?(published: true)
19
+ #
20
+ class WhereExists < Cop
21
+ MSG = 'Prefer `%<good_method>s` over `%<bad_method>s`.'
22
+
23
+ def_node_matcher :where_exists_call?, <<~PATTERN
24
+ (send (send _ :where $...) :exists?)
25
+ PATTERN
26
+
27
+ def on_send(node)
28
+ where_exists_call?(node) do |args|
29
+ return unless convertable_args?(args)
30
+
31
+ range = correction_range(node)
32
+ message = format(MSG, good_method: build_good_method(args), bad_method: range.source)
33
+ add_offense(node, location: range, message: message)
34
+ end
35
+ end
36
+
37
+ def autocorrect(node)
38
+ args = where_exists_call?(node)
39
+
40
+ lambda do |corrector|
41
+ corrector.replace(
42
+ correction_range(node),
43
+ build_good_method(args)
44
+ )
45
+ end
46
+ end
47
+
48
+ private
49
+
50
+ def convertable_args?(args)
51
+ args.size > 1 || args[0].hash_type? || args[0].array_type?
52
+ end
53
+
54
+ def correction_range(node)
55
+ node.receiver.loc.selector.join(node.loc.selector)
56
+ end
57
+
58
+ def build_good_method(args)
59
+ if args.size > 1
60
+ "exists?([#{args.map(&:source).join(', ')}])"
61
+ else
62
+ "exists?(#{args[0].source})"
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
@@ -6,6 +6,7 @@ require_relative 'mixin/target_rails_version'
6
6
 
7
7
  require_relative 'rails/action_filter'
8
8
  require_relative 'rails/active_record_aliases'
9
+ require_relative 'rails/active_record_callbacks_order'
9
10
  require_relative 'rails/active_record_override'
10
11
  require_relative 'rails/active_support_aliases'
11
12
  require_relative 'rails/application_controller'
@@ -16,8 +17,10 @@ require_relative 'rails/assert_not'
16
17
  require_relative 'rails/belongs_to'
17
18
  require_relative 'rails/blank'
18
19
  require_relative 'rails/bulk_change_table'
20
+ require_relative 'rails/content_tag'
19
21
  require_relative 'rails/create_table_with_timestamps'
20
22
  require_relative 'rails/date'
23
+ require_relative 'rails/default_scope'
21
24
  require_relative 'rails/delegate'
22
25
  require_relative 'rails/delegate_allow_blank'
23
26
  require_relative 'rails/dynamic_find_by'
@@ -27,6 +30,7 @@ require_relative 'rails/environment_comparison'
27
30
  require_relative 'rails/exit'
28
31
  require_relative 'rails/file_path'
29
32
  require_relative 'rails/find_by'
33
+ require_relative 'rails/find_by_id'
30
34
  require_relative 'rails/find_each'
31
35
  require_relative 'rails/has_and_belongs_to_many'
32
36
  require_relative 'rails/has_many_or_has_one_dependent'
@@ -36,31 +40,44 @@ require_relative 'rails/http_status'
36
40
  require_relative 'rails/ignored_skip_action_filter_option'
37
41
  require_relative 'rails/index_by'
38
42
  require_relative 'rails/index_with'
43
+ require_relative 'rails/inquiry'
39
44
  require_relative 'rails/inverse_of'
40
45
  require_relative 'rails/lexically_scoped_action_filter'
41
46
  require_relative 'rails/link_to_blank'
47
+ require_relative 'rails/mailer_name'
48
+ require_relative 'rails/match_route'
49
+ require_relative 'rails/negate_include'
42
50
  require_relative 'rails/not_null_column'
43
51
  require_relative 'rails/output'
44
52
  require_relative 'rails/output_safety'
53
+ require_relative 'rails/pick'
54
+ require_relative 'rails/pluck'
55
+ require_relative 'rails/pluck_id'
56
+ require_relative 'rails/pluck_in_where'
45
57
  require_relative 'rails/pluralization_grammar'
46
58
  require_relative 'rails/presence'
47
59
  require_relative 'rails/present'
48
60
  require_relative 'rails/rake_environment'
49
61
  require_relative 'rails/read_write_attribute'
50
62
  require_relative 'rails/redundant_allow_nil'
63
+ require_relative 'rails/redundant_foreign_key'
51
64
  require_relative 'rails/redundant_receiver_in_with_options'
52
65
  require_relative 'rails/reflection_class_name'
53
66
  require_relative 'rails/refute_methods'
54
67
  require_relative 'rails/relative_date_constant'
68
+ require_relative 'rails/render_inline'
69
+ require_relative 'rails/render_plain_text'
55
70
  require_relative 'rails/request_referer'
56
71
  require_relative 'rails/reversible_migration'
57
72
  require_relative 'rails/safe_navigation'
58
73
  require_relative 'rails/safe_navigation_with_blank'
59
74
  require_relative 'rails/save_bang'
60
75
  require_relative 'rails/scope_args'
76
+ require_relative 'rails/short_i18n'
61
77
  require_relative 'rails/skips_model_validations'
62
78
  require_relative 'rails/time_zone'
63
79
  require_relative 'rails/uniq_before_pluck'
64
80
  require_relative 'rails/unique_validation_without_index'
65
81
  require_relative 'rails/unknown_env'
66
82
  require_relative 'rails/validation'
83
+ require_relative 'rails/where_exists'
@@ -24,16 +24,6 @@ module RuboCop
24
24
  remove_instance_variable(:@schema)
25
25
  end
26
26
 
27
- private
28
-
29
- def load!(target_ruby_version)
30
- path = db_schema_path
31
- return unless path
32
-
33
- ast = parse(path, target_ruby_version)
34
- Schema.new(ast)
35
- end
36
-
37
27
  def db_schema_path
38
28
  path = Pathname.pwd
39
29
  until path.root?
@@ -46,6 +36,16 @@ module RuboCop
46
36
  nil
47
37
  end
48
38
 
39
+ private
40
+
41
+ def load!(target_ruby_version)
42
+ path = db_schema_path
43
+ return unless path
44
+
45
+ ast = parse(path, target_ruby_version)
46
+ Schema.new(ast)
47
+ end
48
+
49
49
  def parse(path, target_ruby_version)
50
50
  klass_name = :"Ruby#{target_ruby_version.to_s.sub('.', '')}"
51
51
  klass = ::Parser.const_get(klass_name)
@@ -5,10 +5,12 @@ module RuboCop
5
5
  module SchemaLoader
6
6
  # Represent db/schema.rb
7
7
  class Schema
8
- attr_reader :tables
8
+ attr_reader :tables, :add_indicies
9
9
 
10
10
  def initialize(ast)
11
11
  @tables = []
12
+ @add_indicies = []
13
+
12
14
  build!(ast)
13
15
  end
14
16
 
@@ -18,6 +20,12 @@ module RuboCop
18
20
  end
19
21
  end
20
22
 
23
+ def add_indicies_by(table_name:)
24
+ add_indicies.select do |add_index|
25
+ add_index.table_name == table_name
26
+ end
27
+ end
28
+
21
29
  private
22
30
 
23
31
  def build!(ast)
@@ -26,6 +34,11 @@ module RuboCop
26
34
  each_table(ast) do |table_def|
27
35
  @tables << Table.new(table_def)
28
36
  end
37
+
38
+ # Compatibility for Rails 4.2.
39
+ each_add_index(ast) do |add_index_def|
40
+ @add_indicies << AddIndex.new(add_index_def)
41
+ end
29
42
  end
30
43
 
31
44
  def each_table(ast)
@@ -40,9 +53,17 @@ module RuboCop
40
53
  yield ast.body
41
54
  end
42
55
  end
56
+
57
+ def each_add_index(ast)
58
+ ast.body.children.each do |node|
59
+ next if !node&.send_type? || !node.method?(:add_index)
60
+
61
+ yield(node)
62
+ end
63
+ end
43
64
  end
44
65
 
45
- # Reprecent a table
66
+ # Represent a table
46
67
  class Table
47
68
  attr_reader :name, :columns, :indices
48
69
 
@@ -60,7 +81,7 @@ module RuboCop
60
81
 
61
82
  def build_columns(node)
62
83
  each_content(node).map do |child|
63
- next unless child.send_type?
84
+ next unless child&.send_type?
64
85
  next if child.method?(:index)
65
86
 
66
87
  Column.new(child)
@@ -69,7 +90,7 @@ module RuboCop
69
90
 
70
91
  def build_indices(node)
71
92
  each_content(node).map do |child|
72
- next unless child.send_type?
93
+ next unless child&.send_type?
73
94
  next unless child.method?(:index)
74
95
 
75
96
  Index.new(child)
@@ -79,7 +100,7 @@ module RuboCop
79
100
  def each_content(node)
80
101
  return enum_for(__method__, node) unless block_given?
81
102
 
82
- case node.body.type
103
+ case node.body&.type
83
104
  when :begin
84
105
  node.body.children.each do |child|
85
106
  yield(child)
@@ -90,7 +111,7 @@ module RuboCop
90
111
  end
91
112
  end
92
113
 
93
- # Reprecent a column
114
+ # Represent a column
94
115
  class Column
95
116
  attr_reader :name, :type, :not_null
96
117
 
@@ -116,13 +137,12 @@ module RuboCop
116
137
  end
117
138
  end
118
139
 
119
- # Reprecent an index
140
+ # Represent an index
120
141
  class Index
121
142
  attr_reader :name, :columns, :expression, :unique
122
143
 
123
144
  def initialize(node)
124
- node.first_argument
125
- @columns, @expression = build_columns_or_expr(node)
145
+ @columns, @expression = build_columns_or_expr(node.first_argument)
126
146
  @unique = nil
127
147
 
128
148
  analyze_keywords!(node)
@@ -130,12 +150,11 @@ module RuboCop
130
150
 
131
151
  private
132
152
 
133
- def build_columns_or_expr(node)
134
- arg = node.first_argument
135
- if arg.array_type?
136
- [arg.values.map(&:value), nil]
153
+ def build_columns_or_expr(columns)
154
+ if columns.array_type?
155
+ [columns.values.map(&:value), nil]
137
156
  else
138
- [[], arg.value]
157
+ [[], columns.value]
139
158
  end
140
159
  end
141
160
 
@@ -153,6 +172,21 @@ module RuboCop
153
172
  end
154
173
  end
155
174
  end
175
+
176
+ # Represent an `add_index`
177
+ class AddIndex < Index
178
+ attr_reader :table_name
179
+
180
+ def initialize(node)
181
+ super(node)
182
+
183
+ @table_name = node.first_argument.value
184
+ @columns, @expression = build_columns_or_expr(node.arguments[1])
185
+ @unique = nil
186
+
187
+ analyze_keywords!(node)
188
+ end
189
+ end
156
190
  end
157
191
  end
158
192
  end