rubocop-rails 2.6.0 → 2.7.0

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