rubocop-rails 2.4.2 → 2.7.0

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