rubocop-rails 2.6.0 → 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.
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
6
+ # This cop enforces the use of `ids` over `pluck(:id)` and `pluck(primary_key)`.
7
+ #
8
+ # @example
9
+ # # bad
10
+ # User.pluck(:id)
11
+ # user.posts.pluck(:id)
12
+ #
13
+ # def self.user_ids
14
+ # pluck(primary_key)
15
+ # end
16
+ #
17
+ # # good
18
+ # User.ids
19
+ # user.posts.ids
20
+ #
21
+ # def self.user_ids
22
+ # ids
23
+ # end
24
+ #
25
+ class PluckId < Cop
26
+ include RangeHelp
27
+ include ActiveRecordHelper
28
+
29
+ MSG = 'Use `ids` instead of `%<bad_method>s`.'
30
+
31
+ def_node_matcher :pluck_id_call?, <<~PATTERN
32
+ (send _ :pluck {(sym :id) (send nil? :primary_key)})
33
+ PATTERN
34
+
35
+ def on_send(node)
36
+ return if !pluck_id_call?(node) || in_where?(node)
37
+
38
+ range = offense_range(node)
39
+ message = format(MSG, bad_method: range.source)
40
+
41
+ add_offense(node, location: range, message: message)
42
+ end
43
+
44
+ def autocorrect(node)
45
+ lambda do |corrector|
46
+ corrector.replace(offense_range(node), 'ids')
47
+ end
48
+ end
49
+
50
+ private
51
+
52
+ def offense_range(node)
53
+ range_between(node.loc.selector.begin_pos, node.loc.expression.end_pos)
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
6
+ # This cop identifies places where `pluck` is used in `where` query methods
7
+ # and can be replaced with `select`.
8
+ #
9
+ # Since `pluck` is an eager method and hits the database immediately,
10
+ # using `select` helps to avoid additional database queries.
11
+ #
12
+ # @example
13
+ # # bad
14
+ # Post.where(user_id: User.active.pluck(:id))
15
+ #
16
+ # # good
17
+ # Post.where(user_id: User.active.select(:id))
18
+ #
19
+ class PluckInWhere < Cop
20
+ include ActiveRecordHelper
21
+
22
+ MSG = 'Use `select` instead of `pluck` within `where` query method.'
23
+
24
+ def on_send(node)
25
+ add_offense(node, location: :selector) if node.method?(:pluck) && in_where?(node)
26
+ end
27
+
28
+ def autocorrect(node)
29
+ lambda do |corrector|
30
+ corrector.replace(node.loc.selector, 'select')
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
6
+ # This cop looks for inline rendering within controller actions.
7
+ #
8
+ # @example
9
+ # # bad
10
+ # class ProductsController < ApplicationController
11
+ # def index
12
+ # render inline: "<% products.each do |p| %><p><%= p.name %></p><% end %>", type: :erb
13
+ # end
14
+ # end
15
+ #
16
+ # # good
17
+ # # app/views/products/index.html.erb
18
+ # # <% products.each do |p| %>
19
+ # # <p><%= p.name %></p>
20
+ # # <% end %>
21
+ #
22
+ # class ProductsController < ApplicationController
23
+ # def index
24
+ # end
25
+ # end
26
+ #
27
+ class RenderInline < Cop
28
+ MSG = 'Prefer using a template over inline rendering.'
29
+
30
+ def_node_matcher :render_with_options?, <<~PATTERN
31
+ (send nil? :render $(hash ...))
32
+ PATTERN
33
+
34
+ def on_send(node)
35
+ render_with_options?(node) do |options|
36
+ add_offense(node) if includes_inline_key?(options)
37
+ end
38
+ end
39
+
40
+ private
41
+
42
+ def includes_inline_key?(node)
43
+ node.keys.find { |key| key.value.to_sym == :inline }
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
6
+ # This cop identifies places where `render text:` can be
7
+ # replaced with `render plain:`.
8
+ #
9
+ # @example
10
+ # # bad - explicit MIME type to `text/plain`
11
+ # render text: 'Ruby!', content_type: 'text/plain'
12
+ #
13
+ # # good - short and precise
14
+ # render plain: 'Ruby!'
15
+ #
16
+ # # good - explicit MIME type not to `text/plain`
17
+ # render text: 'Ruby!', content_type: 'text/html'
18
+ #
19
+ # @example ContentTypeCompatibility: true (default)
20
+ # # good - sets MIME type to `text/html`
21
+ # render text: 'Ruby!'
22
+ #
23
+ # @example ContentTypeCompatibility: false
24
+ # # bad - sets MIME type to `text/html`
25
+ # render text: 'Ruby!'
26
+ #
27
+ class RenderPlainText < Cop
28
+ MSG = 'Prefer `render plain:` over `render text:`.'
29
+
30
+ def_node_matcher :render_plain_text?, <<~PATTERN
31
+ (send nil? :render $(hash <$(pair (sym :text) $_) ...>))
32
+ PATTERN
33
+
34
+ def on_send(node)
35
+ render_plain_text?(node) do |options_node, _option_node, _option_value|
36
+ content_type_node = find_content_type(options_node)
37
+ add_offense(node) if compatible_content_type?(content_type_node)
38
+ end
39
+ end
40
+
41
+ def autocorrect(node)
42
+ render_plain_text?(node) do |options_node, option_node, option_value|
43
+ content_type_node = find_content_type(options_node)
44
+ rest_options = options_node.pairs - [option_node, content_type_node].compact
45
+
46
+ lambda do |corrector|
47
+ corrector.replace(
48
+ node,
49
+ replacement(rest_options, option_value)
50
+ )
51
+ end
52
+ end
53
+ end
54
+
55
+ private
56
+
57
+ def find_content_type(node)
58
+ node.pairs.find { |p| p.key.value.to_sym == :content_type }
59
+ end
60
+
61
+ def compatible_content_type?(node)
62
+ (node && node.value.value == 'text/plain') ||
63
+ (!node && !cop_config['ContentTypeCompatibility'])
64
+ end
65
+
66
+ def replacement(rest_options, option_value)
67
+ if rest_options.any?
68
+ "render plain: #{option_value.source}, #{rest_options.map(&:source).join(', ')}"
69
+ else
70
+ "render plain: #{option_value.source}"
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
@@ -48,7 +48,7 @@ module RuboCop
48
48
  def on_send(node)
49
49
  try_call(node) do |try_method, dispatch|
50
50
  return if try_method == :try && !cop_config['ConvertTry']
51
- return unless dispatch.sym_type? && dispatch.value =~ /\w+[=!?]?/
51
+ return unless dispatch.sym_type? && dispatch.value.match?(/\w+[=!?]?/)
52
52
 
53
53
  add_offense(node, message: format(MSG, try: try_method))
54
54
  end
@@ -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,12 +42,18 @@ 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
59
  {
@@ -56,16 +62,30 @@ module RuboCop
56
62
  }
57
63
  PATTERN
58
64
 
65
+ def_node_matcher :good_insert?, <<~PATTERN
66
+ (send _ {:insert :insert!} _ {
67
+ !(hash ...)
68
+ (hash <(pair (sym !{:returning :unique_by}) _) ...>)
69
+ } ...)
70
+ PATTERN
71
+
59
72
  def on_send(node)
60
- return if whitelist.include?(node.method_name.to_s)
61
- 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)
62
75
  return if allowed_method?(node)
63
76
  return if good_touch?(node)
77
+ return if good_insert?(node)
64
78
 
65
79
  add_offense(node, location: :selector)
66
80
  end
67
81
  alias on_csend on_send
68
82
 
83
+ def initialize(*)
84
+ super
85
+ @displayed_allowed_warning = false
86
+ @displayed_forbidden_warning = false
87
+ end
88
+
69
89
  private
70
90
 
71
91
  def message(node)
@@ -77,12 +97,27 @@ module RuboCop
77
97
  !node.arguments?
78
98
  end
79
99
 
80
- def blacklist
81
- 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'] || []
82
109
  end
83
110
 
84
- def whitelist
85
- 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'] || []
86
121
  end
87
122
  end
88
123
  end
@@ -23,7 +23,7 @@ module RuboCop
23
23
  #
24
24
  # @example EnforcedStyle: conservative (default)
25
25
  # # bad
26
- # Model.pluck(:id).distinct
26
+ # Model.pluck(:id).uniq
27
27
  #
28
28
  # # good
29
29
  # Model.distinct.pluck(:id)
@@ -31,14 +31,14 @@ module RuboCop
31
31
  # @example EnforcedStyle: aggressive
32
32
  # # bad
33
33
  # # this will return a Relation that pluck is called on
34
- # Model.where(cond: true).pluck(:id).distinct
34
+ # Model.where(cond: true).pluck(:id).uniq
35
35
  #
36
36
  # # bad
37
37
  # # an association on an instance will return a CollectionProxy
38
- # instance.assoc.pluck(:id).distinct
38
+ # instance.assoc.pluck(:id).uniq
39
39
  #
40
40
  # # bad
41
- # Model.pluck(:id).distinct
41
+ # Model.pluck(:id).uniq
42
42
  #
43
43
  # # good
44
44
  # Model.distinct.pluck(:id)
@@ -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