rubocop-rails 2.5.0 → 2.7.1

Sign up to get free protection for your applications and to get access to all the features.
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