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
@@ -9,14 +9,14 @@ module RuboCop
9
9
  #
10
10
  # This will allow:
11
11
  #
12
- # - update or save calls, assigned to a variable,
12
+ # * update or save calls, assigned to a variable,
13
13
  # or used as a condition in an if/unless/case statement.
14
- # - create calls, assigned to a variable that then has a
14
+ # * create calls, assigned to a variable that then has a
15
15
  # call to `persisted?`, or whose return value is checked by
16
16
  # `persisted?` immediately
17
- # - calls if the result is explicitly returned from methods and blocks,
17
+ # * calls if the result is explicitly returned from methods and blocks,
18
18
  # or provided as arguments.
19
- # - calls whose signature doesn't look like an ActiveRecord
19
+ # * calls whose signature doesn't look like an ActiveRecord
20
20
  # persistence method.
21
21
  #
22
22
  # By default it will also allow implicit returns from methods and blocks.
@@ -218,9 +218,7 @@ module RuboCop
218
218
  def check_used_in_condition_or_compound_boolean(node)
219
219
  return false unless in_condition_or_compound_boolean?(node)
220
220
 
221
- unless MODIFY_PERSIST_METHODS.include?(node.method_name)
222
- add_offense_for_node(node, CREATE_CONDITIONAL_MSG)
223
- end
221
+ add_offense_for_node(node, CREATE_CONDITIONAL_MSG) unless MODIFY_PERSIST_METHODS.include?(node.method_name)
224
222
 
225
223
  true
226
224
  end
@@ -248,6 +246,7 @@ module RuboCop
248
246
 
249
247
  def allowed_receiver?(node)
250
248
  return false unless node.receiver
249
+ return true if node.receiver.const_name == 'ENV'
251
250
  return false unless cop_config['AllowedReceivers']
252
251
 
253
252
  cop_config['AllowedReceivers'].any? do |allowed_receiver|
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
6
+ # This cop enforces that short forms of `I18n` methods are used:
7
+ # `t` instead of `translate` and `l` instead of `localize`.
8
+ #
9
+ # This cop has two different enforcement modes. When the EnforcedStyle
10
+ # is conservative (the default) then only `I18n.translate` and `I18n.localize`
11
+ # calls are added as offenses.
12
+ #
13
+ # When the EnforcedStyle is aggressive then all `translate` and `localize` calls
14
+ # without a receiver are added as offenses.
15
+ #
16
+ # @example
17
+ # # bad
18
+ # I18n.translate :key
19
+ # I18n.localize Time.now
20
+ #
21
+ # # good
22
+ # I18n.t :key
23
+ # I18n.l Time.now
24
+ #
25
+ # @example EnforcedStyle: conservative (default)
26
+ # # good
27
+ # translate :key
28
+ # localize Time.now
29
+ # t :key
30
+ # l Time.now
31
+ #
32
+ # @example EnforcedStyle: aggressive
33
+ # # bad
34
+ # translate :key
35
+ # localize Time.now
36
+ #
37
+ # # good
38
+ # t :key
39
+ # l Time.now
40
+ #
41
+ class ShortI18n < Cop
42
+ include ConfigurableEnforcedStyle
43
+
44
+ MSG = 'Use `%<good_method>s` instead of `%<bad_method>s`.'
45
+
46
+ PREFERRED_METHODS = {
47
+ translate: :t,
48
+ localize: :l
49
+ }.freeze
50
+
51
+ def_node_matcher :long_i18n?, <<~PATTERN
52
+ (send {nil? (const nil? :I18n)} ${:translate :localize} ...)
53
+ PATTERN
54
+
55
+ def on_send(node)
56
+ return if style == :conservative && !node.receiver
57
+
58
+ long_i18n?(node) do |method_name|
59
+ good_method = PREFERRED_METHODS[method_name]
60
+ message = format(MSG, good_method: good_method, bad_method: method_name)
61
+
62
+ add_offense(node, location: :selector, message: message)
63
+ end
64
+ end
65
+
66
+ def autocorrect(node)
67
+ long_i18n?(node) do |method_name|
68
+ lambda do |corrector|
69
+ corrector.replace(node.loc.selector, PREFERRED_METHODS[method_name])
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
@@ -42,27 +42,50 @@ module RuboCop
42
42
  decrement_counter
43
43
  increment!
44
44
  increment_counter
45
+ insert
46
+ insert!
47
+ insert_all
48
+ insert_all!
45
49
  toggle!
46
50
  update_all
47
51
  update_attribute
48
52
  update_column
49
53
  update_columns
50
- update_counters].freeze
54
+ update_counters
55
+ upsert
56
+ upsert_all].freeze
51
57
 
52
58
  def_node_matcher :good_touch?, <<~PATTERN
53
- (send (const nil? :FileUtils) :touch ...)
59
+ {
60
+ (send (const nil? :FileUtils) :touch ...)
61
+ (send _ :touch {true false})
62
+ }
63
+ PATTERN
64
+
65
+ def_node_matcher :good_insert?, <<~PATTERN
66
+ (send _ {:insert :insert!} _ {
67
+ !(hash ...)
68
+ (hash <(pair (sym !{:returning :unique_by}) _) ...>)
69
+ } ...)
54
70
  PATTERN
55
71
 
56
72
  def on_send(node)
57
- return if whitelist.include?(node.method_name.to_s)
58
- return unless blacklist.include?(node.method_name.to_s)
73
+ return if allowed_methods.include?(node.method_name.to_s)
74
+ return unless forbidden_methods.include?(node.method_name.to_s)
59
75
  return if allowed_method?(node)
60
76
  return if good_touch?(node)
77
+ return if good_insert?(node)
61
78
 
62
79
  add_offense(node, location: :selector)
63
80
  end
64
81
  alias on_csend on_send
65
82
 
83
+ def initialize(*)
84
+ super
85
+ @displayed_allowed_warning = false
86
+ @displayed_forbidden_warning = false
87
+ end
88
+
66
89
  private
67
90
 
68
91
  def message(node)
@@ -74,12 +97,27 @@ module RuboCop
74
97
  !node.arguments?
75
98
  end
76
99
 
77
- def blacklist
78
- cop_config['Blacklist'] || []
100
+ def forbidden_methods
101
+ obsolete_result = cop_config['Blacklist']
102
+ if obsolete_result
103
+ warn '`Blacklist` has been renamed to `ForbiddenMethods`.' unless @displayed_forbidden_warning
104
+ @displayed_forbidden_warning = true
105
+ return obsolete_result
106
+ end
107
+
108
+ cop_config['ForbiddenMethods'] || []
79
109
  end
80
110
 
81
- def whitelist
82
- cop_config['Whitelist'] || []
111
+ def allowed_methods
112
+ obsolete_result = cop_config['Whitelist']
113
+ if obsolete_result
114
+ warn '`Whitelist` has been renamed to `AllowedMethods`.' unless @displayed_allowed_warning
115
+ @displayed_allowed_warning = true
116
+
117
+ return obsolete_result
118
+ end
119
+
120
+ cop_config['AllowedMethods'] || []
83
121
  end
84
122
  end
85
123
  end
@@ -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
 
@@ -0,0 +1,155 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
6
+ # When you define a uniqueness validation in Active Record model,
7
+ # you also should add a unique index for the column. There are two reasons
8
+ # First, duplicated records may occur even if Active Record's validation
9
+ # is defined.
10
+ # Second, it will cause slow queries. The validation executes a `SELECT`
11
+ # statement with the target column when inserting/updating a record.
12
+ # If the column does not have an index and the table is large,
13
+ # the query will be heavy.
14
+ #
15
+ # Note that the cop does nothing if db/schema.rb does not exist.
16
+ #
17
+ # @example
18
+ # # bad - if the schema does not have a unique index
19
+ # validates :account, uniqueness: true
20
+ #
21
+ # # good - if the schema has a unique index
22
+ # validates :account, uniqueness: true
23
+ #
24
+ # # good - even if the schema does not have a unique index
25
+ # validates :account, length: { minimum: MIN_LENGTH }
26
+ #
27
+ class UniqueValidationWithoutIndex < Cop
28
+ include ActiveRecordHelper
29
+
30
+ MSG = 'Uniqueness validation should be with a unique index.'
31
+
32
+ def on_send(node)
33
+ return unless node.method?(:validates)
34
+ return unless uniqueness_part(node)
35
+ return if condition_part?(node)
36
+ return unless schema
37
+ return if with_index?(node)
38
+
39
+ add_offense(node)
40
+ end
41
+
42
+ private
43
+
44
+ def with_index?(node)
45
+ klass = class_node(node)
46
+ return true unless klass # Skip analysis
47
+
48
+ table = schema.table_by(name: table_name(klass))
49
+ return true unless table # Skip analysis if it can't find the table
50
+
51
+ names = column_names(node)
52
+ return true unless names
53
+
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)
69
+ end
70
+ end
71
+
72
+ def column_names(node)
73
+ arg = node.first_argument
74
+ return unless arg.str_type? || arg.sym_type?
75
+
76
+ ret = [arg.value]
77
+ names_from_scope = column_names_from_scope(node)
78
+ ret.concat(names_from_scope) if names_from_scope
79
+
80
+ ret.map! do |name|
81
+ klass = class_node(node)
82
+ resolve_relation_into_column(
83
+ name: name.to_s,
84
+ class_node: klass,
85
+ table: schema.table_by(name: table_name(klass))
86
+ )
87
+ end
88
+ ret.include?(nil) ? nil : ret.to_set
89
+ end
90
+
91
+ def column_names_from_scope(node)
92
+ uniq = uniqueness_part(node)
93
+ return unless uniq.hash_type?
94
+
95
+ scope = find_scope(uniq)
96
+ return unless scope
97
+
98
+ case scope.type
99
+ when :sym, :str
100
+ [scope.value]
101
+ when :array
102
+ array_node_to_array(scope)
103
+ end
104
+ end
105
+
106
+ def find_scope(pairs)
107
+ pairs.each_pair.find do |pair|
108
+ key = pair.key
109
+ next unless key.sym_type? && key.value == :scope
110
+
111
+ break pair.value
112
+ end
113
+ end
114
+
115
+ def class_node(node)
116
+ node.each_ancestor.find(&:class_type?)
117
+ end
118
+
119
+ def uniqueness_part(node)
120
+ pairs = node.arguments.last
121
+ return unless pairs.hash_type?
122
+
123
+ pairs.each_pair.find do |pair|
124
+ next unless pair.key.sym_type? && pair.key.value == :uniqueness
125
+
126
+ break pair.value
127
+ end
128
+ end
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
+
142
+ def array_node_to_array(node)
143
+ node.values.map do |elm|
144
+ case elm.type
145
+ when :str, :sym
146
+ elm.value
147
+ else
148
+ return nil
149
+ end
150
+ end
151
+ end
152
+ end
153
+ end
154
+ end
155
+ end
@@ -15,8 +15,6 @@ module RuboCop
15
15
  # Rails.env.production?
16
16
  # Rails.env == 'production'
17
17
  class UnknownEnv < Cop
18
- include NameSimilarity
19
-
20
18
  MSG = 'Unknown environment `%<name>s`.'
21
19
  MSG_SIMILAR = 'Unknown environment `%<name>s`. ' \
22
20
  'Did you mean `%<similar>s`?'
@@ -57,11 +55,14 @@ module RuboCop
57
55
 
58
56
  def message(name)
59
57
  name = name.to_s.chomp('?')
60
- similar = find_similar_name(name, [])
61
- if similar
62
- format(MSG_SIMILAR, name: name, similar: similar)
63
- else
58
+
59
+ spell_checker = DidYouMean::SpellChecker.new(dictionary: environments)
60
+ similar_names = spell_checker.correct(name)
61
+
62
+ if similar_names.empty?
64
63
  format(MSG, name: name)
64
+ else
65
+ format(MSG_SIMILAR, name: name, similar: similar_names.join(', '))
65
66
  end
66
67
  end
67
68