brew vendor-gems: commit updates.

This commit is contained in:
BrewTestBot 2020-12-09 06:30:52 +00:00
parent e557e79668
commit 211a889ce8
108 changed files with 1108 additions and 813 deletions

View File

@ -8,7 +8,7 @@ $:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/i18n-1.8.5/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/minitest-5.14.2/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/thread_safe-0.3.6/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/tzinfo-1.2.8/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/zeitwerk-2.4.1/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/zeitwerk-2.4.2/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/activesupport-6.0.3.4/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/ast-2.4.1/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/bindata-2.4.8/lib"
@ -79,7 +79,7 @@ $:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/ruby-progressbar-1.10
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/unicode-display_width-1.7.0/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/rubocop-1.3.1/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/rubocop-performance-1.9.1/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/rubocop-rails-2.8.1/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/rubocop-rails-2.9.0/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/rubocop-rspec-2.0.1/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/rubocop-sorbet-0.5.1/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/ruby-macho-2.5.0/lib"

View File

@ -98,6 +98,12 @@ Rails/ApplicationRecord:
VersionAdded: '0.49'
VersionChanged: '2.5'
Rails/ArelStar:
Description: 'Enforces `Arel.star` instead of `"*"` for expanded columns.'
Enabled: true
SafeAutoCorrect: false
VersionAdded: '2.9'
Rails/AssertNot:
Description: 'Use `assert_not` instead of `assert !`.'
Enabled: true
@ -105,6 +111,13 @@ Rails/AssertNot:
Include:
- '**/test/**/*'
Rails/AttributeDefaultBlockValue:
Description: 'Pass method call in block for attribute option `default`.'
Enabled: pending
VersionAdded: '2.9'
Include:
- 'models/**/*'
Rails/BelongsTo:
Description: >-
Use `optional: true` instead of `required: false` for
@ -269,8 +282,15 @@ Rails/FindEach:
StyleGuide: 'https://rails.rubystyle.guide#find-each'
Enabled: true
VersionAdded: '0.30'
VersionChanged: '2.9'
Include:
- app/models/**/*.rb
IgnoredMethods:
# Methods that don't work well with `find_each`.
- order
- limit
- select
- lock
Rails/HasAndBelongsToMany:
Description: 'Prefer has_many :through to has_and_belongs_to_many.'
@ -387,7 +407,9 @@ Rails/NegateInclude:
Description: 'Prefer `collection.exclude?(obj)` over `!collection.include?(obj)`.'
StyleGuide: 'https://rails.rubystyle.guide#exclude'
Enabled: 'pending'
Safe: false
VersionAdded: '2.7'
VersionChanged: '2.9'
Rails/NotNullColumn:
Description: 'Do not add a NOT NULL column without a default value.'
@ -653,6 +675,10 @@ Rails/SquishedSQLHeredocs:
StyleGuide: 'https://rails.rubystyle.guide/#squished-heredocs'
Enabled: 'pending'
VersionAdded: '2.8'
VersionChanged: '2.9'
# Some SQL syntax (e.g. PostgreSQL comments and functions) requires newlines
# to be preserved in order to work, thus auto-correction is not safe.
SafeAutoCorrect: false
Rails/TimeZone:
Description: 'Checks the correct usage of time zone aware methods.'
@ -705,6 +731,12 @@ Rails/Validation:
Include:
- app/models/**/*.rb
Rails/WhereEquals:
Description: 'Pass conditions to `where` as a hash instead of manually constructing SQL.'
StyleGuide: 'https://rails.rubystyle.guide/#hash-conditions'
Enabled: 'pending'
VersionAdded: '2.9'
Rails/WhereExists:
Description: 'Prefer `exists?(...)` over `where(...).exists?`.'
Enabled: 'pending'
@ -717,7 +749,7 @@ Rails/WhereExists:
Rails/WhereNot:
Description: 'Use `where.not(...)` instead of manually constructing negated SQL in `where`.'
StyleGuide: 'https://rails.rubystyle.guide/#where-not'
StyleGuide: 'https://rails.rubystyle.guide/#hash-conditions'
Enabled: 'pending'
VersionAdded: '2.8'

View File

@ -35,10 +35,11 @@ module RuboCop
table_name = find_set_table_name(class_node).to_a.last&.first_argument
return table_name.value.to_s if table_name
namespaces = class_node.each_ancestor(:class, :module)
[class_node, *namespaces]
class_nodes = class_node.defined_module.each_node
namespaces = class_node.each_ancestor(:class, :module).map(&:identifier)
[*class_nodes, *namespaces]
.reverse
.map { |klass| klass.identifier.children[1] }.join('_')
.map { |node| node.children[1] }.join('_')
.tableize
end

View File

@ -0,0 +1,40 @@
# frozen_string_literal: true
module RuboCop
module Cop
# Common functionality for enforcing a specific superclass.
module EnforceSuperclass
def self.included(base)
base.def_node_matcher :class_definition, <<~PATTERN
(class (const _ !:#{base::SUPERCLASS}) #{base::BASE_PATTERN} ...)
PATTERN
base.def_node_matcher :class_new_definition, <<~PATTERN
[!^(casgn {nil? cbase} :#{base::SUPERCLASS} ...)
!^^(casgn {nil? cbase} :#{base::SUPERCLASS} (block ...))
(send (const {nil? cbase} :Class) :new #{base::BASE_PATTERN})]
PATTERN
end
def on_class(node)
class_definition(node) do
register_offense(node.children[1])
end
end
def on_send(node)
class_new_definition(node) do
register_offense(node.children.last)
end
end
private
def register_offense(offense_node)
add_offense(offense_node) do |corrector|
corrector.replace(offense_node.source_range, self.class::SUPERCLASS)
end
end
end
end
end

View File

@ -4,6 +4,8 @@ module RuboCop
module Cop
# Common functionality for Rails/IndexBy and Rails/IndexWith
module IndexMethod # rubocop:disable Metrics/ModuleLength
RESTRICT_ON_SEND = %i[each_with_object to_h map collect []].freeze
def on_block(node)
on_bad_each_with_object(node) do |*match|
handle_possible_offense(node, match, 'each_with_object')
@ -32,13 +34,6 @@ module RuboCop
end
end
def autocorrect(node)
lambda do |corrector|
correction = prepare_correction(node)
execute_correction(corrector, node, correction)
end
end
private
# @abstract Implemented with `def_node_matcher`
@ -67,9 +62,11 @@ module RuboCop
return if captures.noop_transformation?
add_offense(
node,
message: "Prefer `#{new_method_name}` over `#{match_desc}`."
)
node, message: "Prefer `#{new_method_name}` over `#{match_desc}`."
) do |corrector|
correction = prepare_correction(node)
execute_correction(corrector, node, correction)
end
end
def extract_captures(match)
@ -119,7 +116,7 @@ module RuboCop
end
# Internal helper class to hold autocorrect data
Autocorrection = Struct.new(:match, :block_node, :leading, :trailing) do # rubocop:disable Metrics/BlockLength
Autocorrection = Struct.new(:match, :block_node, :leading, :trailing) do
def self.from_each_with_object(node, match)
new(match, node, 0, 0)
end

View File

@ -29,8 +29,9 @@ module RuboCop
# after_filter :do_stuff
# append_around_filter :do_stuff
# skip_after_filter :do_stuff
class ActionFilter < Cop
class ActionFilter < Base
include ConfigurableEnforcedStyle
extend AutoCorrector
MSG = 'Prefer `%<prefer>s` over `%<current>s`.'
@ -66,6 +67,8 @@ module RuboCop
skip_action_callback
].freeze
RESTRICT_ON_SEND = FILTER_METHODS + ACTION_METHODS
def on_block(node)
check_method_node(node.send_node)
end
@ -74,24 +77,17 @@ module RuboCop
check_method_node(node) unless node.receiver
end
def autocorrect(node)
lambda do |corrector|
corrector.replace(node.loc.selector,
preferred_method(node.loc.selector.source).to_s)
end
end
private
def check_method_node(node)
return unless bad_methods.include?(node.method_name)
method_name = node.method_name
return unless bad_methods.include?(method_name)
add_offense(node, location: :selector)
end
message = format(MSG, prefer: preferred_method(method_name), current: method_name)
def message(node)
format(MSG, prefer: preferred_method(node.method_name),
current: node.method_name)
add_offense(node.loc.selector, message: message) do |corrector|
corrector.replace(node.loc.selector, preferred_method(node.loc.selector.source))
end
end
def bad_methods

View File

@ -12,7 +12,9 @@ module RuboCop
#
# #good
# Book.update!(author: 'Alice')
class ActiveRecordAliases < Cop
class ActiveRecordAliases < Base
extend AutoCorrector
MSG = 'Use `%<prefer>s` instead of `%<current>s`.'
ALIASES = {
@ -20,28 +22,22 @@ module RuboCop
update_attributes!: :update!
}.freeze
def on_send(node)
ALIASES.each do |bad, good|
next unless node.method?(bad)
RESTRICT_ON_SEND = ALIASES.keys.freeze
add_offense(node,
message: format(MSG, prefer: good, current: bad),
location: :selector,
severity: :warning)
break
def on_send(node)
method_name = node.method_name
alias_method = ALIASES[method_name]
add_offense(
node.loc.selector,
message: format(MSG, prefer: alias_method, current: method_name),
severity: :warning
) do |corrector|
corrector.replace(node.loc.selector, alias_method)
end
end
alias on_csend on_send
def autocorrect(node)
lambda do |corrector|
corrector.replace(
node.loc.selector,
ALIASES[node.method_name].to_s
)
end
end
end
end
end

View File

@ -19,7 +19,9 @@ module RuboCop
# after_commit :after_commit_callback
# end
#
class ActiveRecordCallbacksOrder < Cop
class ActiveRecordCallbacksOrder < Base
extend AutoCorrector
MSG = '`%<current>s` is supposed to appear before `%<previous>s`.'
CALLBACKS_IN_ORDER = %i[
@ -55,17 +57,20 @@ module RuboCop
index = CALLBACKS_ORDER_MAP[callback]
if index < previous_index
message = format(MSG, current: callback,
previous: previous_callback)
add_offense(node, message: message)
message = format(MSG, current: callback, previous: previous_callback)
add_offense(node, message: message) do |corrector|
autocorrect(corrector, node)
end
end
previous_index = index
previous_callback = callback
end
end
private
# Autocorrect by swapping between two nodes autocorrecting them
def autocorrect(node)
def autocorrect(corrector, node)
previous = left_siblings_of(node).reverse_each.find do |sibling|
callback?(sibling)
end
@ -73,14 +78,10 @@ module RuboCop
current_range = source_range_with_comment(node)
previous_range = source_range_with_comment(previous)
lambda do |corrector|
corrector.insert_before(previous_range, current_range.source)
corrector.remove(current_range)
end
corrector.insert_before(previous_range, current_range.source)
corrector.remove(current_range)
end
private
def defined_callbacks(class_node)
class_def = class_node.body
@ -121,7 +122,7 @@ module RuboCop
processed_source.comments_before_line(annotation_line)
.reverse_each do |comment|
if comment.location.line == annotation_line
if comment.location.line == annotation_line && !inline_comment?(comment)
first_comment = comment
annotation_line -= 1
end
@ -130,6 +131,10 @@ module RuboCop
start_line_position(first_comment || node)
end
def inline_comment?(comment)
!comment_line?(comment.loc.expression.source_line)
end
def start_line_position(node)
buffer.line_range(node.loc.line).begin_pos - 1
end

View File

@ -24,7 +24,7 @@ module RuboCop
# end
# end
#
class ActiveRecordOverride < Cop
class ActiveRecordOverride < Base
MSG =
'Use %<prefer>s callbacks instead of overriding the Active Record ' \
'method `%<bad>s`.'

View File

@ -19,8 +19,11 @@ module RuboCop
# [1, 2, 'a'].append('b')
# [1, 2, 'a'].prepend('b')
#
class ActiveSupportAliases < Cop
class ActiveSupportAliases < Base
extend AutoCorrector
MSG = 'Use `%<prefer>s` instead of `%<current>s`.'
RESTRICT_ON_SEND = %i[starts_with? ends_with? append prepend].freeze
ALIASES = {
starts_with?: {
@ -39,30 +42,18 @@ module RuboCop
def on_send(node)
ALIASES.each_key do |aliased_method|
register_offense(node, aliased_method) if
public_send(aliased_method, node)
next unless public_send(aliased_method, node)
preferred_method = ALIASES[aliased_method][:original]
message = format(MSG, prefer: preferred_method, current: aliased_method)
add_offense(node, message: message) do |corrector|
next if append(node)
corrector.replace(node.loc.selector, preferred_method)
end
end
end
def autocorrect(node)
return false if append(node)
lambda do |corrector|
method_name = node.loc.selector.source
replacement = ALIASES[method_name.to_sym][:original]
corrector.replace(node.loc.selector, replacement.to_s)
end
end
private
def register_offense(node, method_name)
add_offense(
node,
message: format(MSG, prefer: ALIASES[method_name][:original],
current: method_name)
)
end
end
end
end

View File

@ -31,7 +31,7 @@ module RuboCop
# after_create_commit :log_create_action
# after_update_commit :log_update_action
#
class AfterCommitOverride < Cop
class AfterCommitOverride < Base
MSG = 'There can only be one `after_*_commit :%<name>s` hook defined for a model.'
AFTER_COMMIT_CALLBACKS = %i[

View File

@ -16,7 +16,9 @@ module RuboCop
# class MyController < ActionController::Base
# # ...
# end
class ApplicationController < Cop
class ApplicationController < Base
extend AutoCorrector
MSG = 'Controllers should subclass `ApplicationController`.'
SUPERCLASS = 'ApplicationController'
BASE_PATTERN = '(const (const nil? :ActionController) :Base)'
@ -24,12 +26,6 @@ module RuboCop
# rubocop:disable Layout/ClassStructure
include RuboCop::Cop::EnforceSuperclass
# rubocop:enable Layout/ClassStructure
def autocorrect(node)
lambda do |corrector|
corrector.replace(node.source_range, self.class::SUPERCLASS)
end
end
end
end
end

View File

@ -16,7 +16,8 @@ module RuboCop
# class Rails4Job < ActiveJob::Base
# # ...
# end
class ApplicationJob < Cop
class ApplicationJob < Base
extend AutoCorrector
extend TargetRailsVersion
minimum_target_rails_version 5.0

View File

@ -16,7 +16,8 @@ module RuboCop
# class MyMailer < ActionMailer::Base
# # ...
# end
class ApplicationMailer < Cop
class ApplicationMailer < Base
extend AutoCorrector
extend TargetRailsVersion
minimum_target_rails_version 5.0
@ -28,12 +29,6 @@ module RuboCop
# rubocop:disable Layout/ClassStructure
include RuboCop::Cop::EnforceSuperclass
# rubocop:enable Layout/ClassStructure
def autocorrect(node)
lambda do |corrector|
corrector.replace(node.source_range, self.class::SUPERCLASS)
end
end
end
end
end

View File

@ -16,7 +16,8 @@ module RuboCop
# class Rails4Model < ActiveRecord::Base
# # ...
# end
class ApplicationRecord < Cop
class ApplicationRecord < Base
extend AutoCorrector
extend TargetRailsVersion
minimum_target_rails_version 5.0
@ -28,12 +29,6 @@ module RuboCop
# rubocop:disable Layout/ClassStructure
include RuboCop::Cop::EnforceSuperclass
# rubocop:enable Layout/ClassStructure
def autocorrect(node)
lambda do |corrector|
corrector.replace(node.source_range, self.class::SUPERCLASS)
end
end
end
end
end

View File

@ -0,0 +1,41 @@
# frozen_string_literal: true
module RuboCop
module Cop
module Rails
# This cop prevents usage of `"*"` on an Arel::Table column reference.
#
# Using `arel_table["*"]` causes the outputted string to be a literal
# quoted asterisk (e.g. <tt>`my_model`.`*`</tt>). This causes the
# database to look for a column named <tt>`*`</tt> (or `"*"`) as opposed
# to expanding the column list as one would likely expect.
#
# @example
# # bad
# MyTable.arel_table["*"]
#
# # good
# MyTable.arel_table[Arel.star]
#
class ArelStar < Base
extend AutoCorrector
MSG = 'Use `Arel.star` instead of `"*"` for expanded column lists.'
RESTRICT_ON_SEND = %i[[]].freeze
def_node_matcher :star_bracket?, <<~PATTERN
(send {const (send _ :arel_table)} :[] $(str "*"))
PATTERN
def on_send(node)
return unless (star = star_bracket?(node))
add_offense(star) do |corrector|
corrector.replace(star.loc.expression, 'Arel.star')
end
end
end
end
end
end

View File

@ -13,23 +13,21 @@ module RuboCop
# # good
# assert_not x
#
class AssertNot < RuboCop::Cop::Cop
class AssertNot < Base
extend AutoCorrector
MSG = 'Prefer `assert_not` over `assert !`.'
RESTRICT_ON_SEND = %i[assert].freeze
def_node_matcher :offensive?, '(send nil? :assert (send ... :!) ...)'
def on_send(node)
add_offense(node) if offensive?(node)
end
return unless offensive?(node)
def autocorrect(node)
expression = node.loc.expression
add_offense(node) do |corrector|
expression = node.loc.expression
lambda do |corrector|
corrector.replace(
expression,
corrected_source(expression.source)
)
corrector.replace(expression, corrected_source(expression.source))
end
end

View File

@ -0,0 +1,90 @@
# frozen_string_literal: true
module RuboCop
module Cop
module Rails
# This cop looks for `attribute` class methods that specify a `:default` option
# which value is an array, string literal or method call without a block.
# It will accept all other values, such as string, symbol, integer and float literals
# as well as constants.
#
# @example
# # bad
# class User < ApplicationRecord
# attribute :confirmed_at, :datetime, default: Time.zone.now
# end
#
# # good
# class User < ApplicationRecord
# attribute :confirmed_at, :datetime, default: -> { Time.zone.now }
# end
#
# # bad
# class User < ApplicationRecord
# attribute :roles, :string, array: true, default: []
# end
#
# # good
# class User < ApplicationRecord
# attribute :roles, :string, array: true, default: -> { [] }
# end
#
# # bad
# class User < ApplicationRecord
# attribute :configuration, default: {}
# end
#
# # good
# class User < ApplicationRecord
# attribute :configuration, default: -> { {} }
# end
#
# # good
# class User < ApplicationRecord
# attribute :role, :string, default: :customer
# end
#
# # good
# class User < ApplicationRecord
# attribute :activated, :boolean, default: false
# end
#
# # good
# class User < ApplicationRecord
# attribute :login_count, :integer, default: 0
# end
#
# # good
# class User < ApplicationRecord
# FOO = 123
# attribute :custom_attribute, :integer, default: FOO
# end
class AttributeDefaultBlockValue < Base
extend AutoCorrector
MSG = 'Pass method in a block to `:default` option.'
RESTRICT_ON_SEND = %i[attribute].freeze
TYPE_OFFENDERS = %i[send array hash].freeze
def_node_matcher :default_attribute, <<~PATTERN
(send nil? :attribute _ ?_ (hash <$#attribute ...>))
PATTERN
def_node_matcher :attribute, '(pair (sym :default) $_)'
def on_send(node)
default_attribute(node) do |attribute|
value = attribute.children.last
return unless TYPE_OFFENDERS.any? { |type| value.type == type }
add_offense(value) do |corrector|
expression = default_attribute(node).children.last
corrector.replace(value, "-> { #{expression.source} }")
end
end
end
end
end
end
end

View File

@ -50,7 +50,8 @@ module RuboCop
#
# @see https://guides.rubyonrails.org/5_0_release_notes.html
# @see https://github.com/rails/rails/pull/18937
class BelongsTo < Cop
class BelongsTo < Base
extend AutoCorrector
extend TargetRailsVersion
minimum_target_rails_version 5.0
@ -64,6 +65,7 @@ module RuboCop
'option is deprecated and you want to use `optional: false`. ' \
'In most configurations, this is the default and you can omit ' \
'this option altogether'
RESTRICT_ON_SEND = %i[belongs_to].freeze
def_node_matcher :match_belongs_to_with_options, <<~PATTERN
(send _ :belongs_to _
@ -72,27 +74,16 @@ module RuboCop
PATTERN
def on_send(node)
match_belongs_to_with_options(node) do |_option_node, option_value|
message =
match_belongs_to_with_options(node) do |option_node, option_value|
message, replacement =
if option_value.true_type?
SUPERFLOUS_REQUIRE_TRUE_MSG
[SUPERFLOUS_REQUIRE_TRUE_MSG, 'optional: false']
elsif option_value.false_type?
SUPERFLOUS_REQUIRE_FALSE_MSG
[SUPERFLOUS_REQUIRE_FALSE_MSG, 'optional: true']
end
add_offense(node, message: message, location: :selector)
end
end
def autocorrect(node)
option_node, option_value = match_belongs_to_with_options(node)
return unless option_node
lambda do |corrector|
if option_value.true_type?
corrector.replace(option_node.loc.expression, 'optional: false')
elsif option_value.false_type?
corrector.replace(option_node.loc.expression, 'optional: true')
add_offense(node.loc.selector, message: message) do |corrector|
corrector.replace(option_node.loc.expression, replacement)
end
end
end

View File

@ -53,11 +53,14 @@ module RuboCop
# def blank?
# !present?
# end
class Blank < Cop
class Blank < Base
extend AutoCorrector
MSG_NIL_OR_EMPTY = 'Use `%<prefer>s` instead of `%<current>s`.'
MSG_NOT_PRESENT = 'Use `%<prefer>s` instead of `%<current>s`.'
MSG_UNLESS_PRESENT = 'Use `if %<prefer>s` instead of ' \
'`%<current>s`.'
RESTRICT_ON_SEND = %i[!].freeze
# `(send nil $_)` is not actually a valid match for an offense. Nodes
# that have a single method call on the left hand side
@ -93,10 +96,10 @@ module RuboCop
# accepts !present? if its in the body of a `blank?` method
next if defining_blank?(node.parent)
add_offense(node,
message: format(MSG_NOT_PRESENT,
prefer: replacement(receiver),
current: node.source))
message = format(MSG_NOT_PRESENT, prefer: replacement(receiver), current: node.source)
add_offense(node, message: message) do |corrector|
autocorrect(corrector, node)
end
end
end
@ -106,10 +109,10 @@ module RuboCop
nil_or_empty?(node) do |var1, var2|
return unless var1 == var2
add_offense(node,
message: format(MSG_NIL_OR_EMPTY,
prefer: replacement(var1),
current: node.source))
message = format(MSG_NIL_OR_EMPTY, prefer: replacement(var1), current: node.source)
add_offense(node, message: message) do |corrector|
autocorrect(corrector, node)
end
end
end
@ -121,32 +124,29 @@ module RuboCop
unless_present?(node) do |method_call, receiver|
range = unless_condition(node, method_call)
add_offense(node,
location: range,
message: format(MSG_UNLESS_PRESENT,
prefer: replacement(receiver),
current: range.source))
end
end
def autocorrect(node)
lambda do |corrector|
method_call, variable1 = unless_present?(node)
if method_call
corrector.replace(node.loc.keyword, 'if')
range = method_call.loc.expression
else
variable1, _variable2 = nil_or_empty?(node) || not_present?(node)
range = node.loc.expression
message = format(MSG_UNLESS_PRESENT, prefer: replacement(receiver), current: range.source)
add_offense(range, message: message) do |corrector|
autocorrect(corrector, node)
end
corrector.replace(range, replacement(variable1))
end
end
private
def autocorrect(corrector, node)
method_call, variable1 = unless_present?(node)
if method_call
corrector.replace(node.loc.keyword, 'if')
range = method_call.loc.expression
else
variable1, _variable2 = nil_or_empty?(node) || not_present?(node)
range = node.loc.expression
end
corrector.replace(range, replacement(variable1))
end
def unless_condition(node, method_call)
if node.modifier_form?
node.loc.keyword.join(node.loc.expression.end)

View File

@ -65,7 +65,7 @@ module RuboCop
#
# @see https://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/SchemaStatements.html#method-i-change_table
# @see https://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/Table.html
class BulkChangeTable < Cop
class BulkChangeTable < Base
MSG_FOR_CHANGE_TABLE = <<~MSG.chomp
You can combine alter queries using `bulk: true` options.
MSG

View File

@ -18,42 +18,42 @@ module RuboCop
# tag.p('Hello world!')
# tag.br
# content_tag(name, 'Hello world!')
class ContentTag < Cop
class ContentTag < Base
include RangeHelp
extend AutoCorrector
extend TargetRailsVersion
minimum_target_rails_version 5.1
MSG = 'Use `tag` instead of `content_tag`.'
RESTRICT_ON_SEND = %i[content_tag].freeze
def on_send(node)
return unless node.method?(:content_tag)
first_argument = node.first_argument
return unless first_argument
return if first_argument.variable? || first_argument.send_type? || first_argument.const_type?
add_offense(node)
end
def autocorrect(node)
lambda do |corrector|
if method_name?(node.first_argument)
range = correction_range(node)
rest_args = node.arguments.drop(1)
replacement = "tag.#{node.first_argument.value.to_s.underscore}(#{rest_args.map(&:source).join(', ')})"
corrector.replace(range, replacement)
else
corrector.replace(node.loc.selector, 'tag')
end
add_offense(node) do |corrector|
autocorrect(corrector, node)
end
end
private
def autocorrect(corrector, node)
if method_name?(node.first_argument)
range = correction_range(node)
rest_args = node.arguments.drop(1)
replacement = "tag.#{node.first_argument.value.to_s.underscore}(#{rest_args.map(&:source).join(', ')})"
corrector.replace(range, replacement)
else
corrector.replace(node.loc.selector, 'tag')
end
end
def method_name?(node)
return false unless node.str_type? || node.sym_type?

View File

@ -40,8 +40,9 @@ module RuboCop
#
# t.datetime :updated_at, default: -> { 'CURRENT_TIMESTAMP' }
# end
class CreateTableWithTimestamps < Cop
class CreateTableWithTimestamps < Base
MSG = 'Add timestamps when creating a new table.'
RESTRICT_ON_SEND = %i[create_table].freeze
def_node_matcher :create_table_with_block?, <<~PATTERN
(block

View File

@ -43,7 +43,7 @@ module RuboCop
# Date.yesterday
# date.in_time_zone
#
class Date < Cop
class Date < Base
include ConfigurableEnforcedStyle
MSG = 'Do not use `Date.%<method_called>s` without zone. Use ' \
@ -52,6 +52,8 @@ module RuboCop
MSG_SEND = 'Do not use `%<method>s` on Date objects, because they ' \
'know nothing about the time zone in use.'
RESTRICT_ON_SEND = %i[to_time to_time_in_current_zone].freeze
BAD_DAYS = %i[today current yesterday tomorrow].freeze
DEPRECATED_METHODS = [
@ -76,8 +78,7 @@ module RuboCop
check_deprecated_methods(node)
add_offense(node, location: :selector,
message: format(MSG_SEND, method: node.method_name))
add_offense(node.loc.selector, message: format(MSG_SEND, method: node.method_name))
end
alias on_csend on_send
@ -87,10 +88,9 @@ module RuboCop
DEPRECATED_METHODS.each do |method|
next unless node.method?(method[:deprecated].to_sym)
add_offense(node, location: :selector,
message: format(DEPRECATED_MSG,
deprecated: method[:deprecated],
relevant: method[:relevant]))
message = format(DEPRECATED_MSG, deprecated: method[:deprecated], relevant: method[:relevant])
add_offense(node.loc.selector, message: message)
end
end
@ -104,10 +104,9 @@ module RuboCop
day = method_name
day = 'today' if method_name == 'current'
add_offense(node, location: :selector,
message: format(MSG,
method_called: method_name,
day: day))
message = format(MSG, method_called: method_name, day: day)
add_offense(node.loc.selector, message: message)
end
def extract_method_chain(node)

View File

@ -22,8 +22,9 @@ module RuboCop
# where(hidden: false)
# end
#
class DefaultScope < Cop
class DefaultScope < Base
MSG = 'Avoid use of `default_scope`. It is better to use explicitly named scopes.'
RESTRICT_ON_SEND = %i[default_scope].freeze
def_node_matcher :method_call?, <<~PATTERN
(send nil? :default_scope ...)
@ -38,15 +39,21 @@ module RuboCop
PATTERN
def on_send(node)
add_offense(node, location: :selector) if method_call?(node)
return unless method_call?(node)
add_offense(node.loc.selector)
end
def on_defs(node)
add_offense(node, location: :name) if class_method_definition?(node)
return unless class_method_definition?(node)
add_offense(node.loc.name)
end
def on_sclass(node)
eigenclass_method_definition?(node) { |default_scope| add_offense(default_scope, location: :name) }
eigenclass_method_definition?(node) do |default_scope|
add_offense(default_scope.loc.name)
end
end
end
end

View File

@ -52,7 +52,9 @@ module RuboCop
#
# # good
# delegate :bar, to: :foo, prefix: true
class Delegate < Cop
class Delegate < Base
extend AutoCorrector
MSG = 'Use `delegate` to define delegations.'
def_node_matcher :delegate?, <<~PATTERN
@ -64,22 +66,20 @@ module RuboCop
return unless trivial_delegate?(node)
return if private_or_protected_delegation(node)
add_offense(node, location: :keyword)
end
def autocorrect(node)
delegation = ["delegate :#{node.body.method_name}",
"to: :#{node.body.receiver.method_name}"]
delegation << ['prefix: true'] if node.method?(prefixed_method_name(node.body))
lambda do |corrector|
corrector.replace(node.source_range, delegation.join(', '))
end
register_offense(node)
end
private
def register_offense(node)
add_offense(node.loc.keyword) do |corrector|
delegation = ["delegate :#{node.body.method_name}", "to: :#{node.body.receiver.method_name}"]
delegation << ['prefix: true'] if node.method?(prefixed_method_name(node.body))
corrector.replace(node.source_range, delegation.join(', '))
end
end
def trivial_delegate?(def_node)
delegate?(def_node) &&
method_name_matches?(def_node.method_name, def_node.body) &&

View File

@ -13,22 +13,21 @@ module RuboCop
#
# # good
# delegate :foo, to: :bar, allow_nil: true
class DelegateAllowBlank < Cop
class DelegateAllowBlank < Base
extend AutoCorrector
MSG = '`allow_blank` is not a valid option, use `allow_nil`.'
RESTRICT_ON_SEND = %i[delegate].freeze
def_node_matcher :allow_blank_option, <<~PATTERN
(send nil? :delegate _ (hash <$(pair (sym :allow_blank) true) ...>))
PATTERN
def on_send(node)
allow_blank_option(node) do |offending_node|
add_offense(offending_node)
end
end
return unless (offending_node = allow_blank_option(node))
def autocorrect(pair_node)
lambda do |corrector|
corrector.replace(pair_node.key.source_range, 'allow_nil')
add_offense(offending_node) do |corrector|
corrector.replace(offending_node.key.source_range, 'allow_nil')
end
end
end

View File

@ -31,7 +31,9 @@ module RuboCop
#
# # good
# Gem::Specification.find_by_name('backend').gem_dir
class DynamicFindBy < Cop
class DynamicFindBy < Base
extend AutoCorrector
MSG = 'Use `%<static_name>s` instead of dynamic `%<method>s`.'
METHOD_PATTERN = /^find_by_(.+?)(!)?$/.freeze
@ -43,25 +45,24 @@ module RuboCop
return unless static_name
return if node.arguments.any?(&:splat_type?)
add_offense(node,
message: format(MSG, static_name: static_name,
method: method_name))
message = format(MSG, static_name: static_name, method: method_name)
add_offense(node, message: message) do |corrector|
autocorrect(corrector, node)
end
end
alias on_csend on_send
def autocorrect(node)
private
def autocorrect(corrector, node)
keywords = column_keywords(node.method_name)
return if keywords.size != node.arguments.size
lambda do |corrector|
autocorrect_method_name(corrector, node)
autocorrect_argument_keywords(corrector, node, keywords)
end
autocorrect_method_name(corrector, node)
autocorrect_argument_keywords(corrector, node, keywords)
end
private
def allowed_invocation?(node)
allowed_method?(node) || allowed_receiver?(node) ||
whitelisted?(node)

View File

@ -17,9 +17,12 @@ module RuboCop
# # good
# enum status: { active: 0, archived: 1 }
#
class EnumHash < Cop
class EnumHash < Base
extend AutoCorrector
MSG = 'Enum defined as an array found in `%<enum>s` enum declaration. '\
'Use hash syntax instead.'
RESTRICT_ON_SEND = %i[enum].freeze
def_node_matcher :enum?, <<~PATTERN
(send nil? :enum (hash $...))
@ -35,19 +38,17 @@ module RuboCop
key, array = array_pair?(pair)
next unless key
add_offense(array, message: format(MSG, enum: enum_name(key)))
add_offense(array, message: format(MSG, enum: enum_name(key))) do |corrector|
hash = array.children.each_with_index.map do |elem, index|
"#{source(elem)} => #{index}"
end.join(', ')
corrector.replace(array.loc.expression, "{#{hash}}")
end
end
end
end
def autocorrect(node)
hash = node.children.each_with_index.map do |elem, index|
"#{source(elem)} => #{index}"
end.join(', ')
->(corrector) { corrector.replace(node.loc.expression, "{#{hash}}") }
end
private
def enum_name(key)

View File

@ -17,11 +17,12 @@ module RuboCop
#
# # good
# enum status: [:active, :archived]
class EnumUniqueness < Cop
class EnumUniqueness < Base
include Duplication
MSG = 'Duplicate value `%<value>s` found in `%<enum>s` ' \
'enum declaration.'
RESTRICT_ON_SEND = %i[enum].freeze
def_node_matcher :enum?, <<~PATTERN
(send nil? :enum (hash $...))

View File

@ -15,12 +15,16 @@ module RuboCop
#
# # good
# Rails.env.production?
class EnvironmentComparison < Cop
class EnvironmentComparison < Base
extend AutoCorrector
MSG = 'Favor `%<bang>sRails.env.%<env>s?` over `%<source>s`.'
SYM_MSG = 'Do not compare `Rails.env` with a symbol, it will always ' \
'evaluate to `false`.'
RESTRICT_ON_SEND = %i[== !=].freeze
def_node_matcher :comparing_str_env_with_rails_env_on_lhs?, <<~PATTERN
(send
(send (const {nil? cbase} :Rails) :env)
@ -62,28 +66,28 @@ module RuboCop
comparing_str_env_with_rails_env_on_rhs?(node))
env, = *env_node
bang = node.method?(:!=) ? '!' : ''
message = format(MSG, bang: bang, env: env, source: node.source)
add_offense(node, message: format(
MSG, bang: bang, env: env, source: node.source
))
add_offense(node, message: message) do |corrector|
autocorrect(corrector, node)
end
end
if comparing_sym_env_with_rails_env_on_lhs?(node) ||
comparing_sym_env_with_rails_env_on_rhs?(node)
add_offense(node, message: SYM_MSG)
end
end
return unless comparing_sym_env_with_rails_env_on_lhs?(node) || comparing_sym_env_with_rails_env_on_rhs?(node)
def autocorrect(node)
lambda do |corrector|
replacement = build_predicate_method(node)
corrector.replace(node.source_range, replacement)
add_offense(node, message: SYM_MSG) do |corrector|
autocorrect(corrector, node)
end
end
private
def autocorrect(corrector, node)
replacement = build_predicate_method(node)
corrector.replace(node.source_range, replacement)
end
def build_predicate_method(node)
if rails_env_on_lhs?(node)
build_predicate_method_for_rails_env_on_lhs(node)

View File

@ -23,27 +23,21 @@ module RuboCop
#
# # good
# raise 'a bad error has happened'
class Exit < Cop
class Exit < Base
include ConfigurableEnforcedStyle
MSG = 'Do not use `exit` in Rails applications.'
TARGET_METHODS = %i[exit exit!].freeze
RESTRICT_ON_SEND = %i[exit exit!].freeze
EXPLICIT_RECEIVERS = %i[Kernel Process].freeze
def on_send(node)
add_offense(node, location: :selector) if offending_node?(node)
add_offense(node.loc.selector) if offending_node?(node)
end
private
def offending_node?(node)
right_method_name?(node.method_name) &&
right_argument_count?(node.arguments) &&
right_receiver?(node.receiver)
end
def right_method_name?(method_name)
TARGET_METHODS.include?(method_name)
right_argument_count?(node.arguments) && right_receiver?(node.receiver)
end
# More than 1 argument likely means it is a different

View File

@ -25,7 +25,7 @@ module RuboCop
# # good
# Rails.root.join('app/models/goober')
#
class FilePath < Cop
class FilePath < Base
include ConfigurableEnforcedStyle
include RangeHelp
@ -33,6 +33,7 @@ module RuboCop
'instead.'
MSG_ARGUMENTS = 'Please use `Rails.root.join(\'path\', \'to\')` ' \
'instead.'
RESTRICT_ON_SEND = %i[join].freeze
def_node_matcher :file_join_nodes?, <<~PATTERN
(send (const nil? :File) :join ...)
@ -97,10 +98,10 @@ module RuboCop
line_range = node.loc.column...node.loc.last_column
source_range = source_range(processed_source.buffer, node.first_line,
line_range)
add_offense(node, location: source_range)
add_offense(source_range)
end
def message(_node)
def message(_range)
format(style == :arguments ? MSG_ARGUMENTS : MSG_SLASHES)
end
end

View File

@ -13,11 +13,12 @@ module RuboCop
#
# # good
# User.find_by(name: 'Bruce')
class FindBy < Cop
class FindBy < Base
include RangeHelp
extend AutoCorrector
MSG = 'Use `find_by` instead of `where.%<method>s`.'
TARGET_SELECTORS = %i[first take].freeze
RESTRICT_ON_SEND = %i[first take].freeze
def_node_matcher :where_first?, <<~PATTERN
(send ({send csend} _ :where ...) {:first :take})
@ -26,28 +27,27 @@ module RuboCop
def on_send(node)
return unless where_first?(node)
range = range_between(node.receiver.loc.selector.begin_pos,
node.loc.selector.end_pos)
range = range_between(node.receiver.loc.selector.begin_pos, node.loc.selector.end_pos)
add_offense(node, location: range,
message: format(MSG, method: node.method_name))
add_offense(range, message: format(MSG, method: node.method_name)) do |corrector|
autocorrect(corrector, node)
end
end
alias on_csend on_send
def autocorrect(node)
private
def autocorrect(corrector, node)
# Don't autocorrect where(...).first, because it can return different
# results from find_by. (They order records differently, so the
# 'first' record can be different.)
return if node.method?(:first)
where_loc = node.receiver.loc.selector
first_loc = range_between(node.loc.dot.begin_pos,
node.loc.selector.end_pos)
first_loc = range_between(node.loc.dot.begin_pos, node.loc.selector.end_pos)
lambda do |corrector|
corrector.replace(where_loc, 'find_by')
corrector.replace(first_loc, '')
end
corrector.replace(where_loc, 'find_by')
corrector.replace(first_loc, '')
end
end
end

View File

@ -16,10 +16,12 @@ module RuboCop
# # good
# User.find(id)
#
class FindById < Cop
class FindById < Base
include RangeHelp
extend AutoCorrector
MSG = 'Use `%<good_method>s` instead of `%<bad_method>s`.'
RESTRICT_ON_SEND = %i[take! find_by_id! find_by!].freeze
def_node_matcher :where_take?, <<~PATTERN
(send
@ -38,41 +40,30 @@ module RuboCop
def on_send(node)
where_take?(node) do |where, id_value|
range = where_take_offense_range(node, where)
good_method = build_good_method(id_value)
bad_method = build_where_take_bad_method(id_value)
message = format(MSG, good_method: good_method, bad_method: bad_method)
add_offense(node, location: range, message: message)
register_offense(range, id_value, bad_method)
end
find_by?(node) do |id_value|
range = find_by_offense_range(node)
good_method = build_good_method(id_value)
bad_method = build_find_by_bad_method(node, id_value)
message = format(MSG, good_method: good_method, bad_method: bad_method)
add_offense(node, location: range, message: message)
end
end
def autocorrect(node)
if (matches = where_take?(node))
where, id_value = *matches
range = where_take_offense_range(node, where)
elsif (id_value = find_by?(node))
range = find_by_offense_range(node)
end
lambda do |corrector|
replacement = build_good_method(id_value)
corrector.replace(range, replacement)
register_offense(range, id_value, bad_method)
end
end
private
def register_offense(range, id_value, bad_method)
good_method = build_good_method(id_value)
message = format(MSG, good_method: good_method, bad_method: bad_method)
add_offense(range, message: message) do |corrector|
corrector.replace(range, good_method)
end
end
def where_take_offense_range(node, where)
range_between(where.loc.selector.begin_pos, node.loc.expression.end_pos)
end

View File

@ -12,27 +12,30 @@ module RuboCop
#
# # good
# User.all.find_each
class FindEach < Cop
#
# @example IgnoredMethods: ['order']
# # good
# User.order(:foo).each
class FindEach < Base
extend AutoCorrector
MSG = 'Use `find_each` instead of `each`.'
RESTRICT_ON_SEND = %i[each].freeze
SCOPE_METHODS = %i[
all eager_load includes joins left_joins left_outer_joins not preload
references unscoped where
].freeze
IGNORED_METHODS = %i[order limit select].freeze
def on_send(node)
return unless node.receiver&.send_type? &&
node.method?(:each)
return unless node.receiver&.send_type?
return unless SCOPE_METHODS.include?(node.receiver.method_name)
return if method_chain(node).any? { |m| ignored_by_find_each?(m) }
return if method_chain(node).any? { |m| ignored?(m) }
add_offense(node, location: :selector)
end
def autocorrect(node)
->(corrector) { corrector.replace(node.loc.selector, 'find_each') }
range = node.loc.selector
add_offense(range) do |corrector|
corrector.replace(range, 'find_each')
end
end
private
@ -41,9 +44,8 @@ module RuboCop
node.each_node(:send).map(&:method_name)
end
def ignored_by_find_each?(relation_method)
# Active Record's #find_each ignores various extra parameters
IGNORED_METHODS.include?(relation_method)
def ignored?(relation_method)
cop_config['IgnoredMethods'].include?(relation_method)
end
end
end

View File

@ -11,13 +11,14 @@ module RuboCop
#
# # good
# # has_many :ingredients, through: :recipe_ingredients
class HasAndBelongsToMany < Cop
class HasAndBelongsToMany < Base
MSG = 'Prefer `has_many :through` to `has_and_belongs_to_many`.'
RESTRICT_ON_SEND = %i[has_and_belongs_to_many].freeze
def on_send(node)
return unless node.command?(:has_and_belongs_to_many)
add_offense(node, location: :selector)
add_offense(node.loc.selector)
end
end
end

View File

@ -20,8 +20,9 @@ module RuboCop
# has_one :avatar, dependent: :destroy
# has_many :patients, through: :appointments
# end
class HasManyOrHasOneDependent < Cop
class HasManyOrHasOneDependent < Base
MSG = 'Specify a `:dependent` option.'
RESTRICT_ON_SEND = %i[has_many has_one].freeze
def_node_search :active_resource_class?, <<~PATTERN
(const (const nil? :ActiveResource) :Base)
@ -55,7 +56,7 @@ module RuboCop
return if !association_without_options?(node) && valid_options?(association_with_options?(node))
return if valid_options_in_with_options_block?(node)
add_offense(node, location: :selector)
add_offense(node.loc.selector)
end
private

View File

@ -23,7 +23,7 @@ module RuboCop
# def welcome_message(user)
# "Hello #{user.name}"
# end
class HelperInstanceVariable < Cop
class HelperInstanceVariable < Base
MSG = 'Do not use instance variables in helpers.'
def on_ivar(node)
@ -33,7 +33,7 @@ module RuboCop
def on_ivasgn(node)
return if node.parent.or_asgn_type?
add_offense(node, location: :name)
add_offense(node.loc.name)
end
end
end

View File

@ -17,7 +17,8 @@ module RuboCop
# # good
# get :new, params: { user_id: 1 }
# get :new, **options
class HttpPositionalArguments < Cop
class HttpPositionalArguments < Base
extend AutoCorrector
extend TargetRailsVersion
MSG = 'Use keyword arguments instead of ' \
@ -25,12 +26,12 @@ module RuboCop
KEYWORD_ARGS = %i[
method params session body flash xhr as headers env to
].freeze
HTTP_METHODS = %i[get post put patch delete head].freeze
RESTRICT_ON_SEND = %i[get post put patch delete head].freeze
minimum_target_rails_version 5.0
def_node_matcher :http_request?, <<~PATTERN
(send nil? {#{HTTP_METHODS.map(&:inspect).join(' ')}} !nil? $_ ...)
(send nil? {#{RESTRICT_ON_SEND.map(&:inspect).join(' ')}} !nil? $_ ...)
PATTERN
def_node_matcher :kwsplat_hash?, <<~PATTERN
@ -41,24 +42,21 @@ module RuboCop
http_request?(node) do |data|
return unless needs_conversion?(data)
add_offense(node, location: :selector,
message: format(MSG, verb: node.method_name))
end
end
message = format(MSG, verb: node.method_name)
# given a pre Rails 5 method: get :new, {user_id: @user.id}, {}
#
# @return lambda of auto correct procedure
# the result should look like:
# get :new, params: { user_id: @user.id }, session: {}
# the http_method is the method used to call the controller
# the controller node can be a symbol, method, object or string
# that represents the path/action on the Rails controller
# the data is the http parameters and environment sent in
# the Rails 5 http call
def autocorrect(node)
lambda do |corrector|
corrector.replace(node.loc.expression, correction(node))
add_offense(node.loc.selector, message: message) do |corrector|
# given a pre Rails 5 method: get :new, {user_id: @user.id}, {}
#
# @return lambda of auto correct procedure
# the result should look like:
# get :new, params: { user_id: @user.id }, session: {}
# the http_method is the method used to call the controller
# the controller node can be a symbol, method, object or string
# that represents the path/action on the Rails controller
# the data is the http parameters and environment sent in
# the Rails 5 http call
corrector.replace(node.loc.expression, correction(node))
end
end
end

View File

@ -31,8 +31,11 @@ module RuboCop
# render plain: 'foo/bar', status: 304
# redirect_to root_url, status: 301
#
class HttpStatus < Cop
class HttpStatus < Base
include ConfigurableEnforcedStyle
extend AutoCorrector
RESTRICT_ON_SEND = %i[render redirect_to].freeze
def_node_matcher :http_status, <<~PATTERN
{
@ -53,14 +56,9 @@ module RuboCop
checker = checker_class.new(status)
return unless checker.offensive?
add_offense(checker.node, message: checker.message)
end
end
def autocorrect(node)
lambda do |corrector|
checker = checker_class.new(node)
corrector.replace(node.loc.expression, checker.preferred_style)
add_offense(checker.node, message: checker.message) do |corrector|
corrector.replace(checker.node.loc.expression, checker.preferred_style)
end
end
end

View File

@ -37,18 +37,20 @@ module RuboCop
# end
#
# @see https://api.rubyonrails.org/classes/AbstractController/Callbacks/ClassMethods.html#method-i-_normalize_callback_options
class IgnoredSkipActionFilterOption < Cop
class IgnoredSkipActionFilterOption < Base
MSG = <<~MSG.chomp.freeze
`%<ignore>s` option will be ignored when `%<prefer>s` and `%<ignore>s` are used together.
MSG
FILTERS = %w[
:skip_after_action
:skip_around_action
:skip_before_action
:skip_action_callback
RESTRICT_ON_SEND = %i[
skip_after_action
skip_around_action
skip_before_action
skip_action_callback
].freeze
FILTERS = RESTRICT_ON_SEND.map { |method_name| ":#{method_name}" }
def_node_matcher :filter_options, <<~PATTERN
(send
nil?

View File

@ -17,8 +17,9 @@ module RuboCop
#
# # good
# [1, 2, 3].index_by { |el| foo(el) }
class IndexBy < Cop
class IndexBy < Base
include IndexMethod
extend AutoCorrector
def_node_matcher :on_bad_each_with_object, <<~PATTERN
(block

View File

@ -17,7 +17,8 @@ module RuboCop
#
# # good
# [1, 2, 3].index_with { |el| foo(el) }
class IndexWith < Cop
class IndexWith < Base
extend AutoCorrector
extend TargetRailsVersion
include IndexMethod

View File

@ -22,15 +22,16 @@ module RuboCop
# pets = %w(cat dog)
# pets.include? 'cat'
#
class Inquiry < Cop
class Inquiry < Base
MSG = "Prefer Ruby's comparison operators over Active Support's `inquiry`."
RESTRICT_ON_SEND = %i[inquiry].freeze
def on_send(node)
return unless node.method?(:inquiry) && node.arguments.empty?
return unless node.arguments.empty?
return unless (receiver = node.receiver)
return if !receiver.str_type? && !receiver.array_type?
add_offense(node, location: :selector)
add_offense(node.loc.selector)
end
end
end

View File

@ -128,10 +128,11 @@ module RuboCop
#
# @see https://guides.rubyonrails.org/association_basics.html#bi-directional-associations
# @see https://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#module-ActiveRecord::Associations::ClassMethods-label-Setting+Inverses
class InverseOf < Cop
class InverseOf < Base
SPECIFY_MSG = 'Specify an `:inverse_of` option.'
NIL_MSG = 'You specified `inverse_of: nil`, you probably meant to ' \
'use `inverse_of: false`.'
RESTRICT_ON_SEND = %i[has_many has_one belongs_to].freeze
def_node_matcher :association_recv_arguments, <<~PATTERN
(send $_ {:has_many :has_one :belongs_to} _ $...)
@ -185,7 +186,7 @@ module RuboCop
return if options_contain_inverse_of?(options)
add_offense(node, message: message(options), location: :selector)
add_offense(node.loc.selector, message: message(options))
end
def scope?(arguments)

View File

@ -82,25 +82,27 @@ module RuboCop
# @content = Article.find(params[:article_id])
# end
# end
class LexicallyScopedActionFilter < Cop
class LexicallyScopedActionFilter < Base
MSG = '%<action>s not explicitly defined on the %<type>s.'
FILTERS = %w[
:after_action
:append_after_action
:append_around_action
:append_before_action
:around_action
:before_action
:prepend_after_action
:prepend_around_action
:prepend_before_action
:skip_after_action
:skip_around_action
:skip_before_action
:skip_action_callback
RESTRICT_ON_SEND = %i[
after_action
append_after_action
append_around_action
append_before_action
around_action
before_action
prepend_after_action
prepend_around_action
prepend_before_action
skip_after_action
skip_around_action
skip_before_action
skip_action_callback
].freeze
FILTERS = RESTRICT_ON_SEND.map { |method_name| ":#{method_name}" }
def_node_matcher :only_or_except_filter_methods, <<~PATTERN
(send
nil?

View File

@ -20,8 +20,11 @@ module RuboCop
#
# # good
# link_to 'Click here', url, target: '_blank', rel: 'noreferrer'
class LinkToBlank < Cop
class LinkToBlank < Base
extend AutoCorrector
MSG = 'Specify a `:rel` option containing noopener.'
RESTRICT_ON_SEND = %i[link_to].freeze
def_node_matcher :blank_target?, <<~PATTERN
(pair {(sym :target) (str "target")} {(str "_blank") (sym :_blank)})
@ -35,39 +38,34 @@ module RuboCop
(pair {(sym :rel) (str "rel")} (str _))
PATTERN
# rubocop:disable Metrics/CyclomaticComplexity
def on_send(node)
return unless node.method?(:link_to)
option_nodes = node.each_child_node(:hash)
option_nodes.map(&:children).each do |options|
blank = options.find { |o| blank_target?(o) }
add_offense(blank) if blank && options.none? { |o| includes_noopener?(o) }
end
end
# rubocop:enable Metrics/CyclomaticComplexity
next unless blank && options.none? { |o| includes_noopener?(o) }
def autocorrect(node)
lambda do |corrector|
send_node = node.parent.parent
option_nodes = send_node.each_child_node(:hash)
rel_node = nil
option_nodes.map(&:children).each do |options|
rel_node ||= options.find { |o| rel_node?(o) }
end
if rel_node
append_to_rel(rel_node, corrector)
else
add_rel(send_node, node, corrector)
add_offense(blank) do |corrector|
autocorrect(corrector, node, blank, option_nodes)
end
end
end
private
def autocorrect(corrector, send_node, node, option_nodes)
rel_node = nil
option_nodes.map(&:children).each do |options|
rel_node ||= options.find { |o| rel_node?(o) }
end
if rel_node
append_to_rel(rel_node, corrector)
else
add_rel(send_node, node, corrector)
end
end
def append_to_rel(rel_node, corrector)
existing_rel = rel_node.children.last.value
str_range = rel_node.children.last.loc.expression.adjust(
@ -89,7 +87,7 @@ module RuboCop
def contains_noopener?(value)
return false unless value
rel_array = value.to_s.split(' ')
rel_array = value.to_s.split
rel_array.include?('noopener') || rel_array.include?('noreferrer')
end
end

View File

@ -23,7 +23,9 @@ module RuboCop
# class UserMailer < ApplicationMailer
# end
#
class MailerName < Cop
class MailerName < Base
extend AutoCorrector
MSG = 'Mailer should end with `Mailer` suffix.'
def_node_matcher :mailer_base_class?, <<~PATTERN
@ -43,7 +45,9 @@ module RuboCop
def on_class(node)
class_definition?(node) do |name_node|
add_offense(name_node)
add_offense(name_node) do |corrector|
autocorrect(corrector, name_node)
end
end
end
@ -54,23 +58,25 @@ module RuboCop
return unless casgn_parent
name = casgn_parent.children[1]
add_offense(casgn_parent, location: :name) unless mailer_suffix?(name)
end
return if mailer_suffix?(name)
def autocorrect(node)
lambda do |corrector|
if node.casgn_type?
name = node.children[1]
corrector.replace(node.loc.name, "#{name}Mailer")
else
name = node.children.last
corrector.replace(node.source_range, "#{name}Mailer")
end
add_offense(casgn_parent.loc.name) do |corrector|
autocorrect(corrector, casgn_parent)
end
end
private
def autocorrect(corrector, node)
if node.casgn_type?
name = node.children[1]
corrector.replace(node.loc.name, "#{name}Mailer")
else
name = node.children.last
corrector.replace(node.source_range, "#{name}Mailer")
end
end
def mailer_suffix?(mailer_name)
mailer_name.to_s.end_with?('Mailer')
end

View File

@ -20,8 +20,11 @@ module RuboCop
# match 'photos/:id', to: 'photos#show', via: [:get, :post]
# match 'photos/:id', to: 'photos#show', via: :all
#
class MatchRoute < Cop
class MatchRoute < Base
extend AutoCorrector
MSG = 'Use `%<http_method>s` instead of `match` to define a route.'
RESTRICT_ON_SEND = %i[match].freeze
HTTP_METHODS = %i[get post put patch delete].freeze
def_node_matcher :match_method_call?, <<~PATTERN
@ -35,30 +38,28 @@ module RuboCop
options_node = path_node.hash_type? ? path_node : options_node.first
if options_node.nil?
message = format(MSG, http_method: 'get')
add_offense(node, message: message)
register_offense(node, 'get')
else
via = extract_via(options_node)
if via.size == 1 && http_method?(via.first)
message = format(MSG, http_method: via.first)
add_offense(node, message: message)
end
end
end
end
return unless via.size == 1 && http_method?(via.first)
def autocorrect(node)
match_method_call?(node) do |path_node, options_node|
options_node = options_node.first
lambda do |corrector|
corrector.replace(node, replacement(path_node, options_node))
register_offense(node, via.first)
end
end
end
private
def register_offense(node, http_method)
add_offense(node, message: format(MSG, http_method: http_method)) do |corrector|
match_method_call?(node) do |path_node, options_node|
options_node = options_node.first
corrector.replace(node, replacement(path_node, options_node))
end
end
end
def_node_matcher :routes_draw?, <<~PATTERN
(send (send _ :routes) :draw)
PATTERN

View File

@ -6,6 +6,9 @@ module RuboCop
# This cop enforces the use of `collection.exclude?(obj)`
# over `!collection.include?(obj)`.
#
# It is marked as unsafe by default because false positive will occur for
# a receiver object that do not have `exclude?` method. (e.g. `IPAddr`)
#
# @example
# # bad
# !array.include?(2)
@ -15,22 +18,21 @@ module RuboCop
# array.exclude?(2)
# hash.exclude?(:key)
#
class NegateInclude < Cop
class NegateInclude < Base
extend AutoCorrector
MSG = 'Use `.exclude?` and remove the negation part.'
RESTRICT_ON_SEND = %i[!].freeze
def_node_matcher :negate_include_call?, <<~PATTERN
(send (send $_ :include? $_) :!)
PATTERN
def on_send(node)
add_offense(node) if negate_include_call?(node)
end
return unless (receiver, obj = negate_include_call?(node))
def autocorrect(node)
negate_include_call?(node) do |receiver, obj|
lambda do |corrector|
corrector.replace(node, "#{receiver.source}.exclude?(#{obj.source})")
end
add_offense(node) do |corrector|
corrector.replace(node, "#{receiver.source}.exclude?(#{obj.source})")
end
end
end

View File

@ -16,8 +16,9 @@ module RuboCop
# add_column :users, :name, :string, null: false, default: ''
# add_reference :products, :category
# add_reference :products, :category, null: false, default: 1
class NotNullColumn < Cop
class NotNullColumn < Base
MSG = 'Do not add a NOT NULL column without a default value.'
RESTRICT_ON_SEND = %i[add_column add_reference].freeze
def_node_matcher :add_not_null_column?, <<~PATTERN
(send nil? :add_column _ _ _ (hash $...))

View File

@ -25,6 +25,7 @@ module RuboCop
MSG = 'Do not use the `id` column for ordering. '\
'Use a timestamp column to order chronologically.'
RESTRICT_ON_SEND = %i[order].freeze
def_node_matcher :order_by_id?, <<~PATTERN
(send _ :order
@ -37,8 +38,6 @@ module RuboCop
PATTERN
def on_send(node)
return unless node.method?(:order)
add_offense(offense_range(node)) if order_by_id?(node)
end

View File

@ -13,9 +13,12 @@ module RuboCop
#
# # good
# Rails.logger.debug 'A debug message'
class Output < Cop
class Output < Base
MSG = 'Do not write to stdout. ' \
"Use Rails's logger if you want to log."
RESTRICT_ON_SEND = %i[
ap p pp pretty_print print puts binwrite syswrite write write_nonblock
].freeze
def_node_matcher :output?, <<~PATTERN
(send nil? {:ap :p :pp :pretty_print :print :puts} ...)
@ -35,7 +38,7 @@ module RuboCop
return unless (output?(node) || io_output?(node)) &&
node.arguments?
add_offense(node, location: :selector)
add_offense(node.loc.selector)
end
private

View File

@ -62,8 +62,9 @@ module RuboCop
# safe_join([user_content, " ", content_tag(:span, user_content)])
# # => ActiveSupport::SafeBuffer
# # "&lt;b&gt;hi&lt;/b&gt; <span>&lt;b&gt;hi&lt;/b&gt;</span>"
class OutputSafety < Cop
class OutputSafety < Base
MSG = 'Tagging a string as html safe may be a security risk.'
RESTRICT_ON_SEND = %i[html_safe raw safe_concat].freeze
def on_send(node)
return if non_interpolated_string?(node)
@ -72,7 +73,7 @@ module RuboCop
looks_like_rails_raw?(node) ||
looks_like_rails_safe_concat?(node)
add_offense(node, location: :selector)
add_offense(node.loc.selector)
end
alias on_csend on_send

View File

@ -17,10 +17,12 @@ module RuboCop
# # good
# Model.pick(:a)
# [{ a: :b, c: :d }].pick(:a, :b)
class Pick < Cop
class Pick < Base
extend AutoCorrector
extend TargetRailsVersion
MSG = 'Prefer `pick(%<args>s)` over `pluck(%<args>s).first`.'
RESTRICT_ON_SEND = %i[first].freeze
minimum_target_rails_version 6.0
@ -30,24 +32,24 @@ module RuboCop
def on_send(node)
pick_candidate?(node) do
range = node.receiver.loc.selector.join(node.loc.selector)
add_offense(node, location: range)
end
end
receiver = node.receiver
receiver_selector = receiver.loc.selector
node_selector = node.loc.selector
range = receiver_selector.join(node_selector)
def autocorrect(node)
first_range = node.receiver.source_range.end.join(node.loc.selector)
add_offense(range, message: message(receiver)) do |corrector|
first_range = receiver.source_range.end.join(node_selector)
lambda do |corrector|
corrector.remove(first_range)
corrector.replace(node.receiver.loc.selector, 'pick')
corrector.remove(first_range)
corrector.replace(receiver_selector, 'pick')
end
end
end
private
def message(node)
format(MSG, args: node.receiver.arguments.map(&:source).join(', '))
def message(receiver)
format(MSG, args: receiver.arguments.map(&:source).join(', '))
end
end
end

View File

@ -17,7 +17,8 @@ module RuboCop
# # good
# Post.published.pluck(:title)
# [{ a: :b, c: :d }].pluck(:a)
class Pluck < Cop
class Pluck < Base
extend AutoCorrector
extend TargetRailsVersion
MSG = 'Prefer `pluck(:%<value>s)` over `%<method>s { |%<argument>s| %<element>s[:%<value>s] }`.'
@ -32,15 +33,11 @@ module RuboCop
pluck_candidate?(node) do |method, argument, element, value|
next unless argument == element
add_offense(node, location: offense_range(node), message: message(method, argument, element, value))
end
end
message = message(method, argument, element, value)
def autocorrect(node)
_method, _argument, _element, value = pluck_candidate?(node)
lambda do |corrector|
corrector.replace(offense_range(node), "pluck(:#{value})")
add_offense(offense_range(node), message: message) do |corrector|
corrector.replace(offense_range(node), "pluck(:#{value})")
end
end
end

View File

@ -22,11 +22,13 @@ module RuboCop
# ids
# end
#
class PluckId < Cop
class PluckId < Base
include RangeHelp
include ActiveRecordHelper
extend AutoCorrector
MSG = 'Use `ids` instead of `%<bad_method>s`.'
RESTRICT_ON_SEND = %i[pluck].freeze
def_node_matcher :pluck_id_call?, <<~PATTERN
(send _ :pluck {(sym :id) (send nil? :primary_key)})
@ -38,11 +40,7 @@ module RuboCop
range = offense_range(node)
message = format(MSG, bad_method: range.source)
add_offense(node, location: range, message: message)
end
def autocorrect(node)
lambda do |corrector|
add_offense(range, message: message) do |corrector|
corrector.replace(offense_range(node), 'ids')
end
end

View File

@ -34,22 +34,22 @@ module RuboCop
# # bad
# Post.where(user_id: active_users.pluck(:id))
#
class PluckInWhere < Cop
class PluckInWhere < Base
include ActiveRecordHelper
include ConfigurableEnforcedStyle
extend AutoCorrector
MSG = 'Use `select` instead of `pluck` within `where` query method.'
RESTRICT_ON_SEND = %i[pluck].freeze
def on_send(node)
return unless node.method?(:pluck) && in_where?(node)
return unless in_where?(node)
return if style == :conservative && !root_receiver(node)&.const_type?
add_offense(node, location: :selector)
end
range = node.loc.selector
def autocorrect(node)
lambda do |corrector|
corrector.replace(node.loc.selector, 'select')
add_offense(range) do |corrector|
corrector.replace(range, 'select')
end
end

View File

@ -14,7 +14,9 @@ module RuboCop
# # good
# 3.days.ago
# 1.month.ago
class PluralizationGrammar < Cop
class PluralizationGrammar < Base
extend AutoCorrector
SINGULAR_DURATION_METHODS = { second: :seconds,
minute: :minutes,
hour: :hours,
@ -24,21 +26,18 @@ module RuboCop
month: :months,
year: :years }.freeze
RESTRICT_ON_SEND = SINGULAR_DURATION_METHODS.keys + SINGULAR_DURATION_METHODS.values
PLURAL_DURATION_METHODS = SINGULAR_DURATION_METHODS.invert.freeze
MSG = 'Prefer `%<number>s.%<correct>s`.'
def on_send(node)
return unless duration_method?(node.method_name)
return unless literal_number?(node.receiver)
return unless duration_method?(node.method_name) && literal_number?(node.receiver) && offense?(node)
return unless offense?(node)
number, = *node.receiver
add_offense(node)
end
def autocorrect(node)
lambda do |corrector|
add_offense(node, message: message(number, node.method_name)) do |corrector|
method_name = node.loc.selector.source
corrector.replace(node.loc.selector, correct_method(method_name))
@ -47,11 +46,8 @@ module RuboCop
private
def message(node)
number, = *node.receiver
format(MSG, number: number,
correct: correct_method(node.method_name.to_s))
def message(number, method_name)
format(MSG, number: number, correct: correct_method(method_name))
end
def correct_method(method_name)

View File

@ -37,8 +37,9 @@ module RuboCop
#
# # good
# a.presence || b
class Presence < Cop
class Presence < Base
include RangeHelp
extend AutoCorrector
MSG = 'Use `%<prefer>s` instead of `%<current>s`.'
@ -76,28 +77,26 @@ module RuboCop
return if ignore_if_node?(node)
redundant_receiver_and_other(node) do |receiver, other|
add_offense(node, message: message(node, receiver, other)) unless ignore_other_node?(other) || receiver.nil?
return if ignore_other_node?(other) || receiver.nil?
register_offense(node, receiver, other)
end
redundant_negative_receiver_and_other(node) do |receiver, other|
add_offense(node, message: message(node, receiver, other)) unless ignore_other_node?(other) || receiver.nil?
end
end
return if ignore_other_node?(other) || receiver.nil?
def autocorrect(node)
lambda do |corrector|
redundant_receiver_and_other(node) do |receiver, other|
corrector.replace(node.source_range, replacement(receiver, other))
end
redundant_negative_receiver_and_other(node) do |receiver, other|
corrector.replace(node.source_range, replacement(receiver, other))
end
register_offense(node, receiver, other)
end
end
private
def register_offense(node, receiver, other)
add_offense(node, message: message(node, receiver, other)) do |corrector|
corrector.replace(node.source_range, replacement(receiver, other))
end
end
def ignore_if_node?(node)
node.elsif?
end

View File

@ -43,12 +43,15 @@ module RuboCop
#
# # good
# something if foo.present?
class Present < Cop
class Present < Base
extend AutoCorrector
MSG_NOT_BLANK = 'Use `%<prefer>s` instead of `%<current>s`.'
MSG_EXISTS_AND_NOT_EMPTY = 'Use `%<prefer>s` instead of ' \
'`%<current>s`.'
MSG_UNLESS_BLANK = 'Use `if %<prefer>s` instead of ' \
'`%<current>s`.'
RESTRICT_ON_SEND = %i[!].freeze
def_node_matcher :exists_and_not_empty?, <<~PATTERN
(and
@ -74,10 +77,11 @@ module RuboCop
return unless cop_config['NotBlank']
not_blank?(node) do |receiver|
add_offense(node,
message: format(MSG_NOT_BLANK,
prefer: replacement(receiver),
current: node.source))
message = format(MSG_NOT_BLANK, prefer: replacement(receiver), current: node.source)
add_offense(node, message: message) do |corrector|
autocorrect(corrector, node)
end
end
end
@ -87,10 +91,11 @@ module RuboCop
exists_and_not_empty?(node) do |var1, var2|
return unless var1 == var2
add_offense(node,
message: format(MSG_EXISTS_AND_NOT_EMPTY,
prefer: replacement(var1),
current: node.source))
message = format(MSG_EXISTS_AND_NOT_EMPTY, prefer: replacement(var1), current: node.source)
add_offense(node, message: message) do |corrector|
autocorrect(corrector, node)
end
end
end
@ -100,7 +105,9 @@ module RuboCop
exists_and_not_empty?(node) do |var1, var2|
return unless var1 == var2
add_offense(node, message: MSG_EXISTS_AND_NOT_EMPTY)
add_offense(node, message: MSG_EXISTS_AND_NOT_EMPTY) do |corrector|
autocorrect(corrector, node)
end
end
end
@ -113,25 +120,24 @@ module RuboCop
range = unless_condition(node, method_call)
msg = format(MSG_UNLESS_BLANK, prefer: replacement(receiver),
current: range.source)
add_offense(node, location: range, message: msg)
add_offense(range, message: msg) do |corrector|
autocorrect(corrector, node)
end
end
end
def autocorrect(node)
lambda do |corrector|
method_call, variable1 = unless_blank?(node)
def autocorrect(corrector, node)
method_call, variable1 = unless_blank?(node)
if method_call
corrector.replace(node.loc.keyword, 'if')
range = method_call.loc.expression
else
variable1, _variable2 =
exists_and_not_empty?(node) || not_blank?(node)
range = node.loc.expression
end
corrector.replace(range, replacement(variable1))
if method_call
corrector.replace(node.loc.keyword, 'if')
range = method_call.loc.expression
else
variable1, _variable2 = exists_and_not_empty?(node) || not_blank?(node)
range = node.loc.expression
end
corrector.replace(range, replacement(variable1))
end
private

View File

@ -25,7 +25,9 @@ module RuboCop
# do_something
# end
#
class RakeEnvironment < Cop
class RakeEnvironment < Base
extend AutoCorrector
MSG = 'Include `:environment` task as a dependency for all Rake tasks.'
def_node_matcher :task_definition?, <<~PATTERN
@ -37,16 +39,12 @@ module RuboCop
return if task_name(task_method) == :default
return if with_dependencies?(task_method)
add_offense(task_method)
end
end
add_offense(task_method) do |corrector|
task_name = task_method.arguments[0]
task_dependency = correct_task_dependency(task_name)
def autocorrect(node)
lambda do |corrector|
task_name = node.arguments[0]
task_dependency = correct_task_dependency(task_name)
corrector.replace(task_name.loc.expression, task_dependency)
corrector.replace(task_name.loc.expression, task_dependency)
end
end
end

View File

@ -23,8 +23,11 @@ module RuboCop
# # good
# x = self[:attr]
# self[:attr] = val
class ReadWriteAttribute < Cop
class ReadWriteAttribute < Base
extend AutoCorrector
MSG = 'Prefer `%<prefer>s` over `%<current>s`.'
RESTRICT_ON_SEND = %i[read_attribute write_attribute].freeze
def_node_matcher :read_write_attribute?, <<~PATTERN
{
@ -36,18 +39,16 @@ module RuboCop
def on_send(node)
return unless read_write_attribute?(node)
add_offense(node, location: :selector)
end
add_offense(node.loc.selector, message: message(node)) do |corrector|
case node.method_name
when :read_attribute
replacement = read_attribute_replacement(node)
when :write_attribute
replacement = write_attribute_replacement(node)
end
def autocorrect(node)
case node.method_name
when :read_attribute
replacement = read_attribute_replacement(node)
when :write_attribute
replacement = write_attribute_replacement(node)
corrector.replace(node.source_range, replacement)
end
->(corrector) { corrector.replace(node.source_range, replacement) }
end
private

View File

@ -26,8 +26,9 @@ module RuboCop
# # Here, `nil` is valid but `''` is not
# validates :x, length: { is: 5 }, allow_nil: true, allow_blank: false
#
class RedundantAllowNil < Cop
class RedundantAllowNil < Base
include RangeHelp
extend AutoCorrector
MSG_SAME =
'`allow_nil` is redundant when `allow_blank` has the same value.'
@ -35,59 +36,56 @@ module RuboCop
MSG_ALLOW_NIL_FALSE =
'`allow_nil: false` is redundant when `allow_blank` is true.'
def on_send(node)
return unless node.method?(:validates)
RESTRICT_ON_SEND = %i[validates].freeze
def on_send(node)
allow_nil, allow_blank = find_allow_nil_and_allow_blank(node)
return unless allow_nil && allow_blank
allow_nil_val = allow_nil.children.last
allow_blank_val = allow_blank.children.last
offense(allow_nil_val, allow_blank_val, allow_nil)
end
def autocorrect(node)
prv_sib = previous_sibling(node)
nxt_sib = next_sibling(node)
lambda do |corrector|
if nxt_sib
corrector.remove(range_between(node_beg(node), node_beg(nxt_sib)))
elsif prv_sib
corrector.remove(range_between(node_end(prv_sib), node_end(node)))
else
corrector.remove(node.loc.expression)
end
if allow_nil_val.type == allow_blank_val.type
register_offense(allow_nil, MSG_SAME)
elsif allow_nil_val.false_type? && allow_blank_val.true_type?
register_offense(allow_nil, MSG_ALLOW_NIL_FALSE)
end
end
private
def offense(allow_nil_val, allow_blank_val, allow_nil)
if allow_nil_val.type == allow_blank_val.type
add_offense(allow_nil, message: MSG_SAME)
elsif allow_nil_val.false_type? && allow_blank_val.true_type?
add_offense(allow_nil, message: MSG_ALLOW_NIL_FALSE)
def register_offense(allow_nil, message)
add_offense(allow_nil, message: message) do |corrector|
prv_sib = previous_sibling(allow_nil)
nxt_sib = next_sibling(allow_nil)
if nxt_sib
corrector.remove(range_between(node_beg(allow_nil), node_beg(nxt_sib)))
elsif prv_sib
corrector.remove(range_between(node_end(prv_sib), node_end(allow_nil)))
else
corrector.remove(allow_nil.loc.expression)
end
end
end
def find_allow_nil_and_allow_blank(node)
allow_nil = nil
allow_blank = nil
allow_nil, allow_blank = nil
node.each_descendant do |descendant|
next unless descendant.pair_type?
node.each_child_node do |child_node|
if child_node.pair_type?
key = child_node.children.first.source
key = descendant.children.first.source
allow_nil = child_node if key == 'allow_nil'
allow_blank = child_node if key == 'allow_blank'
end
return [allow_nil, allow_blank] if allow_nil && allow_blank
allow_nil = descendant if key == 'allow_nil'
allow_blank = descendant if key == 'allow_blank'
break if allow_nil && allow_blank
found_in_children_nodes = find_allow_nil_and_allow_blank(child_node)
return found_in_children_nodes if found_in_children_nodes
end
[allow_nil, allow_blank]
nil
end
def previous_sibling(node)

View File

@ -24,10 +24,12 @@ module RuboCop
# class Comment
# belongs_to :author, foreign_key: 'user_id'
# end
class RedundantForeignKey < Cop
class RedundantForeignKey < Base
include RangeHelp
extend AutoCorrector
MSG = 'Specifying the default value for `foreign_key` is redundant.'
RESTRICT_ON_SEND = %i[belongs_to has_one has_many has_and_belongs_to_many].freeze
def_node_matcher :association_with_foreign_key, <<~PATTERN
(send nil? ${:belongs_to :has_one :has_many :has_and_belongs_to_many} ({sym str} $_)
@ -38,21 +40,16 @@ module RuboCop
def on_send(node)
association_with_foreign_key(node) do |type, name, options, foreign_key_pair, foreign_key|
if redundant?(node, type, name, options, foreign_key)
add_offense(node, location: foreign_key_pair.loc.expression)
add_offense(foreign_key_pair.loc.expression) do |corrector|
range = range_with_surrounding_space(range: foreign_key_pair.source_range, side: :left)
range = range_with_surrounding_comma(range, :left)
corrector.remove(range)
end
end
end
end
def autocorrect(node)
_type, _name, _options, foreign_key_pair, _foreign_key = association_with_foreign_key(node)
range = range_with_surrounding_space(range: foreign_key_pair.source_range, side: :left)
range = range_with_surrounding_comma(range, :left)
lambda do |corrector|
corrector.remove(range)
end
end
private
def redundant?(node, association_type, association_name, options, foreign_key)

View File

@ -54,8 +54,9 @@ module RuboCop
# merger.invoke(another_receiver)
# end
# end
class RedundantReceiverInWithOptions < Cop
class RedundantReceiverInWithOptions < Base
include RangeHelp
extend AutoCorrector
MSG = 'Redundant receiver in `with_options`.'
@ -86,22 +87,22 @@ module RuboCop
if send_nodes.all? { |n| same_value?(arg, n.receiver) }
send_nodes.each do |send_node|
receiver = send_node.receiver
add_offense(send_node, location: receiver.source_range)
add_offense(receiver.source_range) do |corrector|
autocorrect(corrector, send_node)
end
end
end
end
end
def autocorrect(node)
lambda do |corrector|
corrector.remove(node.receiver.source_range)
corrector.remove(node.loc.dot)
corrector.remove(block_argument_range(node))
end
end
private
def autocorrect(corrector, node)
corrector.remove(node.receiver.source_range)
corrector.remove(node.loc.dot)
corrector.remove(block_argument_range(node))
end
def block_argument_range(node)
block_node = node.each_ancestor(:block).first
block_argument = block_node.children[1].source_range

View File

@ -13,8 +13,9 @@ module RuboCop
#
# # good
# has_many :accounts, class_name: 'Account'
class ReflectionClassName < Cop
class ReflectionClassName < Base
MSG = 'Use a string value for `class_name`.'
RESTRICT_ON_SEND = %i[has_many has_one belongs_to].freeze
def_node_matcher :association_with_reflection, <<~PATTERN
(send nil? {:has_many :has_one :belongs_to} _ _ ?
@ -28,7 +29,7 @@ module RuboCop
def on_send(node)
association_with_reflection(node) do |reflection_class_name|
add_offense(node, location: reflection_class_name.loc.expression)
add_offense(reflection_class_name.loc.expression)
end
end
end

View File

@ -28,8 +28,9 @@ module RuboCop
# refute_empty [1, 2, 3]
# refute_equal true, false
#
class RefuteMethods < Cop
class RefuteMethods < Base
include ConfigurableEnforcedStyle
extend AutoCorrector
MSG = 'Prefer `%<good_method>s` over `%<bad_method>s`.'
@ -53,21 +54,19 @@ module RuboCop
REFUTE_METHODS = CORRECTIONS.keys.freeze
ASSERT_NOT_METHODS = CORRECTIONS.values.freeze
RESTRICT_ON_SEND = REFUTE_METHODS + ASSERT_NOT_METHODS
def_node_matcher :offensive?, '(send nil? #bad_method? ...)'
def on_send(node)
return unless offensive?(node)
message = offense_message(node.method_name)
add_offense(node, location: :selector, message: message)
end
method_name = node.method_name
message = offense_message(method_name)
range = node.loc.selector
def autocorrect(node)
bad_method = node.method_name
good_method = convert_good_method(bad_method)
lambda do |corrector|
corrector.replace(node.loc.selector, good_method.to_s)
add_offense(range, message: message) do |corrector|
corrector.replace(range, convert_good_method(method_name))
end
end

View File

@ -27,15 +27,18 @@ module RuboCop
# 1.week.since
# end
# end
class RelativeDateConstant < Cop
class RelativeDateConstant < Base
include RangeHelp
extend AutoCorrector
MSG = 'Do not assign %<method_name>s to constants as it ' \
'will be evaluated only once.'
def on_casgn(node)
relative_date_assignment?(node) do |method_name|
add_offense(node, message: format(MSG, method_name: method_name))
add_offense(node, message: message(method_name)) do |corrector|
autocorrect(corrector, node)
end
end
end
@ -48,9 +51,9 @@ module RuboCop
next unless name.casgn_type?
relative_date?(value) do |method_name|
add_offense(node,
location: offense_range(name, value),
message: format(MSG, method_name: method_name))
add_offense(offense_range(name, value), message: message(method_name)) do |corrector|
autocorrect(corrector, node)
end
end
end
end
@ -61,7 +64,9 @@ module RuboCop
end
end
def autocorrect(node)
private
def autocorrect(corrector, node)
return unless node.casgn_type?
scope, const_name, value = *node
@ -71,10 +76,13 @@ module RuboCop
new_code = ["def self.#{const_name.downcase}",
"#{indent}#{value.source}",
'end'].join("\n#{indent}")
->(corrector) { corrector.replace(node.source_range, new_code) }
corrector.replace(node.source_range, new_code)
end
private
def message(method_name)
format(MSG, method_name: method_name)
end
def offense_range(name, value)
range_between(name.loc.expression.begin_pos, value.loc.expression.end_pos)

View File

@ -24,8 +24,9 @@ module RuboCop
# end
# end
#
class RenderInline < Cop
class RenderInline < Base
MSG = 'Prefer using a template over inline rendering.'
RESTRICT_ON_SEND = %i[render].freeze
def_node_matcher :render_with_inline_option?, <<~PATTERN
(send nil? :render (hash <(pair {(sym :inline) (str "inline")} _) ...>))

View File

@ -24,30 +24,25 @@ module RuboCop
# # bad - sets MIME type to `text/html`
# render text: 'Ruby!'
#
class RenderPlainText < Cop
class RenderPlainText < Base
extend AutoCorrector
MSG = 'Prefer `render plain:` over `render text:`.'
RESTRICT_ON_SEND = %i[render].freeze
def_node_matcher :render_plain_text?, <<~PATTERN
(send nil? :render $(hash <$(pair (sym :text) $_) ...>))
PATTERN
def on_send(node)
render_plain_text?(node) do |options_node, _option_node, _option_value|
content_type_node = find_content_type(options_node)
add_offense(node) if compatible_content_type?(content_type_node)
end
end
def autocorrect(node)
render_plain_text?(node) do |options_node, option_node, option_value|
content_type_node = find_content_type(options_node)
rest_options = options_node.pairs - [option_node, content_type_node].compact
return unless compatible_content_type?(content_type_node)
lambda do |corrector|
corrector.replace(
node,
replacement(rest_options, option_value)
)
add_offense(node) do |corrector|
rest_options = options_node.pairs - [option_node, content_type_node].compact
corrector.replace(node, replacement(rest_options, option_value))
end
end
end

View File

@ -19,11 +19,13 @@ module RuboCop
#
# # good
# request.referrer
class RequestReferer < Cop
class RequestReferer < Base
include ConfigurableEnforcedStyle
extend AutoCorrector
MSG = 'Use `request.%<prefer>s` instead of ' \
'`request.%<current>s`.'
RESTRICT_ON_SEND = %i[referer referrer].freeze
def_node_matcher :referer?, <<~PATTERN
(send (send nil? :request) {:referer :referrer})
@ -33,17 +35,15 @@ module RuboCop
referer?(node) do
return unless node.method?(wrong_method_name)
add_offense(node.source_range, location: node.source_range)
add_offense(node.source_range) do |corrector|
corrector.replace(node, "request.#{style}")
end
end
end
def autocorrect(node)
->(corrector) { corrector.replace(node, "request.#{style}") }
end
private
def message(_node)
def message(_range)
format(MSG, prefer: style, current: wrong_method_name)
end

View File

@ -175,7 +175,7 @@ module RuboCop
# end
#
# @see https://api.rubyonrails.org/classes/ActiveRecord/Migration/CommandRecorder.html
class ReversibleMigration < Cop
class ReversibleMigration < Base
MSG = '%<action>s is not reversible.'
def_node_matcher :irreversible_schema_statement_call, <<~PATTERN
@ -271,11 +271,7 @@ module RuboCop
def check_remove_foreign_key_node(node)
remove_foreign_key_call(node) do |arg|
if arg.hash_type? && !all_hash_key?(arg, :to_table)
add_offense(
node,
message: format(MSG,
action: 'remove_foreign_key(without table)')
)
add_offense(node, message: format(MSG, action: 'remove_foreign_key(without table)'))
end
end
end

View File

@ -36,10 +36,12 @@ module RuboCop
# foo&.bar
# foo&.bar(baz)
# foo&.bar { |e| e.baz }
class SafeNavigation < Cop
class SafeNavigation < Base
include RangeHelp
extend AutoCorrector
MSG = 'Use safe navigation (`&.`) instead of `%<try>s`.'
RESTRICT_ON_SEND = %i[try try!].freeze
def_node_matcher :try_call, <<~PATTERN
(send !nil? ${:try :try!} $_ ...)
@ -50,24 +52,23 @@ module RuboCop
return if try_method == :try && !cop_config['ConvertTry']
return unless dispatch.sym_type? && dispatch.value.match?(/\w+[=!?]?/)
add_offense(node, message: format(MSG, try: try_method))
end
end
def autocorrect(node)
method_node, *params = *node.arguments
method = method_node.source[1..-1]
range = range_between(node.loc.dot.begin_pos,
node.loc.expression.end_pos)
lambda do |corrector|
corrector.replace(range, replacement(method, params))
add_offense(node, message: format(MSG, try: try_method)) do |corrector|
autocorrect(corrector, node)
end
end
end
private
def autocorrect(corrector, node)
method_node, *params = *node.arguments
method = method_node.source[1..-1]
range = range_between(node.loc.dot.begin_pos, node.loc.expression.end_pos)
corrector.replace(range, replacement(method, params))
end
def replacement(method, params)
new_params = params.map(&:source).join(', ')

View File

@ -19,7 +19,9 @@ module RuboCop
# do_something if foo.blank?
# do_something unless foo.blank?
#
class SafeNavigationWithBlank < Cop
class SafeNavigationWithBlank < Base
extend AutoCorrector
MSG =
'Avoid calling `blank?` with the safe navigation operator ' \
'in conditionals.'
@ -31,15 +33,8 @@ module RuboCop
def on_if(node)
return unless safe_navigation_blank_in_conditional?(node)
add_offense(node)
end
def autocorrect(node)
lambda do |corrector|
corrector.replace(
safe_navigation_blank_in_conditional?(node).location.dot,
'.'
)
add_offense(node) do |corrector|
corrector.replace(safe_navigation_blank_in_conditional?(node).location.dot, '.')
end
end
end

View File

@ -98,8 +98,9 @@ module RuboCop
# Services::Service::Mailer.update(message: 'Message')
# Service::Mailer::update
#
class SaveBang < Cop
class SaveBang < Base
include NegativeConditional
extend AutoCorrector
MSG = 'Use `%<prefer>s` instead of `%<current>s` if the return ' \
'value is not checked.'
@ -113,11 +114,10 @@ module RuboCop
first_or_create find_or_create_by].freeze
MODIFY_PERSIST_METHODS = %i[save
update update_attributes destroy].freeze
PERSIST_METHODS = (CREATE_PERSIST_METHODS +
MODIFY_PERSIST_METHODS).freeze
RESTRICT_ON_SEND = (CREATE_PERSIST_METHODS + MODIFY_PERSIST_METHODS).freeze
def join_force?(force_class)
force_class == VariableForce
def self.joining_forces
VariableForce
end
def after_leaving_scope(scope, _variable_table)
@ -135,7 +135,7 @@ module RuboCop
return unless persist_method?(node, CREATE_PERSIST_METHODS)
return if persisted_referenced?(assignment)
add_offense_for_node(node, CREATE_MSG)
register_offense(node, CREATE_MSG)
end
# rubocop:disable Metrics/CyclomaticComplexity
@ -148,25 +148,22 @@ module RuboCop
return if explicit_return?(node)
return if checked_immediately?(node)
add_offense_for_node(node)
register_offense(node, MSG)
end
# rubocop:enable Metrics/CyclomaticComplexity
alias on_csend on_send
def autocorrect(node)
save_loc = node.loc.selector
new_method = "#{node.method_name}!"
->(corrector) { corrector.replace(save_loc, new_method) }
end
private
def add_offense_for_node(node, msg = MSG)
name = node.method_name
full_message = format(msg, prefer: "#{name}!", current: name.to_s)
def register_offense(node, msg)
current_method = node.method_name
bang_method = "#{current_method}!"
full_message = format(msg, prefer: bang_method, current: current_method)
add_offense(node, location: :selector, message: full_message)
range = node.loc.selector
add_offense(range, message: full_message) do |corrector|
corrector.replace(range, bang_method)
end
end
def right_assignment_node(assignment)
@ -218,7 +215,7 @@ module RuboCop
def check_used_in_condition_or_compound_boolean(node)
return false unless in_condition_or_compound_boolean?(node)
add_offense_for_node(node, CREATE_CONDITIONAL_MSG) unless MODIFY_PERSIST_METHODS.include?(node.method_name)
register_offense(node, CREATE_CONDITIONAL_MSG) unless MODIFY_PERSIST_METHODS.include?(node.method_name)
true
end
@ -318,7 +315,7 @@ module RuboCop
assignment&.lvasgn_type?
end
def persist_method?(node, methods = PERSIST_METHODS)
def persist_method?(node, methods = RESTRICT_ON_SEND)
methods.include?(node.method_name) &&
expected_signature?(node) &&
!allowed_receiver?(node)

View File

@ -13,8 +13,9 @@ module RuboCop
#
# # good
# scope :something, -> { where(something: true) }
class ScopeArgs < Cop
class ScopeArgs < Base
MSG = 'Use `lambda`/`proc` instead of a plain method call.'
RESTRICT_ON_SEND = %i[scope].freeze
def_node_matcher :scope?, '(send nil? :scope _ $send)'

View File

@ -38,8 +38,9 @@ module RuboCop
# t :key
# l Time.now
#
class ShortI18n < Cop
class ShortI18n < Base
include ConfigurableEnforcedStyle
extend AutoCorrector
MSG = 'Use `%<good_method>s` instead of `%<bad_method>s`.'
@ -48,6 +49,8 @@ module RuboCop
localize: :l
}.freeze
RESTRICT_ON_SEND = PREFERRED_METHODS.keys.freeze
def_node_matcher :long_i18n?, <<~PATTERN
(send {nil? (const nil? :I18n)} ${:translate :localize} ...)
PATTERN
@ -58,15 +61,10 @@ module RuboCop
long_i18n?(node) do |method_name|
good_method = PREFERRED_METHODS[method_name]
message = format(MSG, good_method: good_method, bad_method: method_name)
range = node.loc.selector
add_offense(node, location: :selector, message: message)
end
end
def autocorrect(node)
long_i18n?(node) do |method_name|
lambda do |corrector|
corrector.replace(node.loc.selector, PREFERRED_METHODS[method_name])
add_offense(range, message: message) do |corrector|
corrector.replace(range, PREFERRED_METHODS[method_name])
end
end
end

View File

@ -7,7 +7,7 @@ module RuboCop
# validations which are listed in
# https://guides.rubyonrails.org/active_record_validations.html#skipping-validations
#
# Methods may be ignored from this rule by configuring a `Whitelist`.
# Methods may be ignored from this rule by configuring a `AllowedMethods`.
#
# @example
# # bad
@ -26,7 +26,7 @@ module RuboCop
# user.update(website: 'example.com')
# FileUtils.touch('file')
#
# @example Whitelist: ["touch"]
# @example AllowedMethods: ["touch"]
# # bad
# DiscussionBoard.decrement_counter(:post_count, 5)
# DiscussionBoard.increment_counter(:post_count, 5)
@ -35,7 +35,7 @@ module RuboCop
# # good
# user.touch
#
class SkipsModelValidations < Cop
class SkipsModelValidations < Base
MSG = 'Avoid using `%<method>s` because it skips validations.'
METHODS_WITH_ARGUMENTS = %w[decrement!
@ -76,7 +76,7 @@ module RuboCop
return if good_touch?(node)
return if good_insert?(node)
add_offense(node, location: :selector)
add_offense(node.loc.selector, message: message(node))
end
alias on_csend on_send

View File

@ -5,6 +5,8 @@ module RuboCop
module Rails
#
# Checks SQL heredocs to use `.squish`.
# Some SQL syntax (e.g. PostgreSQL comments and functions) requires newlines
# to be preserved in order to work, thus auto-correction for this cop is not safe.
#
# @example
# # bad
@ -37,8 +39,9 @@ module RuboCop
# WHERE post_id = 1
# SQL
#
class SquishedSQLHeredocs < Cop
class SquishedSQLHeredocs < Base
include Heredoc
extend AutoCorrector
SQL = 'SQL'
SQUISH = '.squish'
@ -47,11 +50,7 @@ module RuboCop
def on_heredoc(node)
return unless offense_detected?(node)
add_offense(node)
end
def autocorrect(node)
lambda do |corrector|
add_offense(node) do |corrector|
corrector.insert_after(node, SQUISH)
end
end

View File

@ -43,8 +43,9 @@ module RuboCop
# # good
# Time.current
# Time.at(timestamp).in_time_zone
class TimeZone < Cop
class TimeZone < Base
include ConfigurableEnforcedStyle
extend AutoCorrector
MSG = 'Do not use `%<current>s` without zone. Use `%<prefer>s` ' \
'instead.'
@ -71,27 +72,25 @@ module RuboCop
check_time_node(klass, node.parent) if klass == :Time
end
def autocorrect(node)
lambda do |corrector|
# add `.zone`: `Time.at` => `Time.zone.at`
corrector.insert_after(node.children[0].source_range, '.zone')
case node.method_name
when :current
# replace `Time.zone.current` => `Time.zone.now`
corrector.replace(node.loc.selector, 'now')
when :new
autocorrect_time_new(node, corrector)
end
# prefer `Time` over `DateTime` class
corrector.replace(node.children.first.source_range, 'Time') if strict?
remove_redundant_in_time_zone(corrector, node)
end
end
private
def autocorrect(corrector, node)
# add `.zone`: `Time.at` => `Time.zone.at`
corrector.insert_after(node.children[0].source_range, '.zone')
case node.method_name
when :current
# replace `Time.zone.current` => `Time.zone.now`
corrector.replace(node.loc.selector, 'now')
when :new
autocorrect_time_new(node, corrector)
end
# prefer `Time` over `DateTime` class
corrector.replace(node.children.first.source_range, 'Time') if strict?
remove_redundant_in_time_zone(corrector, node)
end
def autocorrect_time_new(node, corrector)
if node.arguments?
corrector.replace(node.loc.selector, 'local')
@ -128,7 +127,9 @@ module RuboCop
message = build_message(klass, method_name, node)
add_offense(node, location: :selector, message: message)
add_offense(node.loc.selector, message: message) do |corrector|
autocorrect(corrector, node)
end
end
def build_message(klass, method_name, node)
@ -193,8 +194,9 @@ module RuboCop
return if node.arguments?
add_offense(selector_node,
location: :selector, message: MSG_LOCALTIME)
add_offense(selector_node.loc.selector, message: MSG_LOCALTIME) do |corrector|
autocorrect(corrector, selector_node)
end
end
def not_danger_chain?(chain)

View File

@ -45,11 +45,13 @@ module RuboCop
# # good
# Model.distinct.pluck(:id)
#
class UniqBeforePluck < RuboCop::Cop::Cop
class UniqBeforePluck < Base
include ConfigurableEnforcedStyle
include RangeHelp
extend AutoCorrector
MSG = 'Use `distinct` before `pluck`.'
RESTRICT_ON_SEND = %i[uniq distinct pluck].freeze
NEWLINE = "\n"
PATTERN = '[!^block (send (send %<type>s :pluck ...) ' \
'${:uniq :distinct} ...)]'
@ -69,11 +71,7 @@ module RuboCop
return unless method
add_offense(node, location: :selector)
end
def autocorrect(node)
lambda do |corrector|
add_offense(node.loc.selector) do |corrector|
method = node.method_name
corrector.remove(dot_method_with_whitespace(method, node))

View File

@ -24,13 +24,13 @@ module RuboCop
# # good - even if the schema does not have a unique index
# validates :account, length: { minimum: MIN_LENGTH }
#
class UniqueValidationWithoutIndex < Cop
class UniqueValidationWithoutIndex < Base
include ActiveRecordHelper
MSG = 'Uniqueness validation should be with a unique index.'
RESTRICT_ON_SEND = %i[validates].freeze
def on_send(node)
return unless node.method?(:validates)
return unless uniqueness_part(node)
return if condition_part?(node)
return unless schema

View File

@ -17,7 +17,7 @@ module RuboCop
# # good
# Rails.env.production?
# Rails.env == 'production'
class UnknownEnv < Cop
class UnknownEnv < Base
MSG = 'Unknown environment `%<name>s`.'
MSG_SIMILAR = 'Unknown environment `%<name>s`. ' \
'Did you mean `%<similar>s`?'
@ -41,7 +41,7 @@ module RuboCop
def on_send(node)
unknown_environment_predicate?(node) do |name|
add_offense(node, location: :selector, message: message(name))
add_offense(node.loc.selector, message: message(name))
end
unknown_environment_equal?(node) do |str_node|

View File

@ -32,7 +32,9 @@ module RuboCop
# validates :foo, size: true
# validates :foo, uniqueness: true
#
class Validation < Cop
class Validation < Base
extend AutoCorrector
MSG = 'Prefer the new style validations `%<prefer>s` over ' \
'`%<current>s`.'
@ -50,22 +52,20 @@ module RuboCop
uniqueness
].freeze
DENYLIST = TYPES.map { |p| "validates_#{p}_of".to_sym }.freeze
RESTRICT_ON_SEND = TYPES.map { |p| "validates_#{p}_of".to_sym }.freeze
ALLOWLIST = TYPES.map { |p| "validates :column, #{p}: value" }.freeze
def on_send(node)
return unless !node.receiver && DENYLIST.include?(node.method_name)
return if node.receiver
add_offense(node, location: :selector)
end
range = node.loc.selector
def autocorrect(node)
last_argument = node.arguments.last
return if !last_argument.literal? && !last_argument.splat_type? &&
!frozen_array_argument?(last_argument)
add_offense(range, message: message(node)) do |corrector|
last_argument = node.arguments.last
return if !last_argument.literal? && !last_argument.splat_type? &&
!frozen_array_argument?(last_argument)
lambda do |corrector|
corrector.replace(node.loc.selector, 'validates')
corrector.replace(range, 'validates')
correct_validate_type(corrector, node)
end
end
@ -73,12 +73,13 @@ module RuboCop
private
def message(node)
format(MSG, prefer: preferred_method(node.method_name),
current: node.method_name)
method_name = node.method_name
format(MSG, prefer: preferred_method(method_name), current: method_name)
end
def preferred_method(method)
ALLOWLIST[DENYLIST.index(method.to_sym)]
ALLOWLIST[RESTRICT_ON_SEND.index(method.to_sym)]
end
def correct_validate_type(corrector, node)

View File

@ -0,0 +1,94 @@
# frozen_string_literal: true
module RuboCop
module Cop
module Rails
# This cop identifies places where manually constructed SQL
# in `where` can be replaced with `where(attribute: value)`.
#
# @example
# # bad
# User.where('name = ?', 'Gabe')
# User.where('name = :name', name: 'Gabe')
# User.where('name IS NULL')
# User.where('name IN (?)', ['john', 'jane'])
# User.where('name IN (:names)', names: ['john', 'jane'])
#
# # good
# User.where(name: 'Gabe')
# User.where(name: nil)
# User.where(name: ['john', 'jane'])
class WhereEquals < Base
include RangeHelp
extend AutoCorrector
MSG = 'Use `%<good_method>s` instead of manually constructing SQL.'
RESTRICT_ON_SEND = %i[where].freeze
def_node_matcher :where_method_call?, <<~PATTERN
{
(send _ :where (array $str_type? $_ ?))
(send _ :where $str_type? $_ ?)
}
PATTERN
def on_send(node)
where_method_call?(node) do |template_node, value_node|
value_node = value_node.first
range = offense_range(node)
column_and_value = extract_column_and_value(template_node, value_node)
return unless column_and_value
good_method = build_good_method(*column_and_value)
message = format(MSG, good_method: good_method)
add_offense(range, message: message) do |corrector|
corrector.replace(range, good_method)
end
end
end
EQ_ANONYMOUS_RE = /\A([\w.]+)\s+=\s+\?\z/.freeze # column = ?
IN_ANONYMOUS_RE = /\A([\w.]+)\s+IN\s+\(\?\)\z/i.freeze # column IN (?)
EQ_NAMED_RE = /\A([\w.]+)\s+=\s+:(\w+)\z/.freeze # column = :column
IN_NAMED_RE = /\A([\w.]+)\s+IN\s+\(:(\w+)\)\z/i.freeze # column IN (:column)
IS_NULL_RE = /\A([\w.]+)\s+IS\s+NULL\z/i.freeze # column IS NULL
private
def offense_range(node)
range_between(node.loc.selector.begin_pos, node.loc.expression.end_pos)
end
def extract_column_and_value(template_node, value_node)
value =
case template_node.value
when EQ_ANONYMOUS_RE, IN_ANONYMOUS_RE
value_node.source
when EQ_NAMED_RE, IN_NAMED_RE
return unless value_node.hash_type?
pair = value_node.pairs.find { |p| p.key.value.to_sym == Regexp.last_match(2).to_sym }
pair.value.source
when IS_NULL_RE
'nil'
else
return
end
[Regexp.last_match(1), value]
end
def build_good_method(column, value)
if column.include?('.')
"where('#{column}' => #{value})"
else
"where(#{column}: #{value})"
end
end
end
end
end
end

View File

@ -36,10 +36,12 @@ module RuboCop
# User.where('name = ?', 'john').exists?
# user.posts.where(published: true).exists?
# User.where('length(name) > 10').exists?
class WhereExists < Cop
class WhereExists < Base
include ConfigurableEnforcedStyle
extend AutoCorrector
MSG = 'Prefer `%<good_method>s` over `%<bad_method>s`.'
RESTRICT_ON_SEND = %i[exists?].freeze
def_node_matcher :where_exists_call?, <<~PATTERN
(send (send _ :where $...) :exists?)
@ -54,19 +56,12 @@ module RuboCop
return unless convertable_args?(args)
range = correction_range(node)
message = format(MSG, good_method: build_good_method(args), bad_method: range.source)
add_offense(node, location: range, message: message)
end
end
good_method = build_good_method(args)
message = format(MSG, good_method: good_method, bad_method: range.source)
def autocorrect(node)
args = find_offenses(node)
lambda do |corrector|
corrector.replace(
correction_range(node),
build_good_method(args)
)
add_offense(range, message: message) do |corrector|
corrector.replace(range, good_method)
end
end
end

View File

@ -21,10 +21,12 @@ module RuboCop
# User.where.not(name: nil)
# User.where.not(name: ['john', 'jane'])
#
class WhereNot < Cop
class WhereNot < Base
include RangeHelp
extend AutoCorrector
MSG = 'Use `%<good_method>s` instead of manually constructing negated SQL in `where`.'
RESTRICT_ON_SEND = %i[where].freeze
def_node_matcher :where_method_call?, <<~PATTERN
{
@ -45,21 +47,8 @@ module RuboCop
good_method = build_good_method(*column_and_value)
message = format(MSG, good_method: good_method)
add_offense(node, location: range, message: message)
end
end
def autocorrect(node)
where_method_call?(node) do |template_node, value_node|
value_node = value_node.first
lambda do |corrector|
range = offense_range(node)
column, value = *extract_column_and_value(template_node, value_node)
replacement = build_good_method(column, value)
corrector.replace(range, replacement)
add_offense(range, message: message) do |corrector|
corrector.replace(range, good_method)
end
end
end

View File

@ -1,6 +1,7 @@
# frozen_string_literal: true
require_relative 'mixin/active_record_helper'
require_relative 'mixin/enforce_superclass'
require_relative 'mixin/index_method'
require_relative 'mixin/target_rails_version'
@ -14,7 +15,9 @@ require_relative 'rails/application_controller'
require_relative 'rails/application_job'
require_relative 'rails/application_mailer'
require_relative 'rails/application_record'
require_relative 'rails/arel_star'
require_relative 'rails/assert_not'
require_relative 'rails/attribute_default_block_value'
require_relative 'rails/belongs_to'
require_relative 'rails/blank'
require_relative 'rails/bulk_change_table'
@ -83,5 +86,6 @@ require_relative 'rails/uniq_before_pluck'
require_relative 'rails/unique_validation_without_index'
require_relative 'rails/unknown_env'
require_relative 'rails/validation'
require_relative 'rails/where_equals'
require_relative 'rails/where_exists'
require_relative 'rails/where_not'

View File

@ -13,15 +13,15 @@ module RuboCop
#
# @return [Schema, nil]
def load(target_ruby_version)
return @schema if defined?(@schema)
return @load if defined?(@load)
@schema = load!(target_ruby_version)
@load = load!(target_ruby_version)
end
def reset!
return unless instance_variable_defined?(:@schema)
return unless instance_variable_defined?(:@load)
remove_instance_variable(:@schema)
remove_instance_variable(:@load)
end
def db_schema_path

View File

@ -98,7 +98,7 @@ module RuboCop
end
def each_content(node, &block)
return enum_for(__method__, node) unless block_given?
return enum_for(__method__, node) unless block
case node.body&.type
when :begin

View File

@ -4,7 +4,11 @@ module RuboCop
module Rails
# This module holds the RuboCop Rails version information.
module Version
STRING = '2.8.1'
STRING = '2.9.0'
def self.document_version
STRING.match('\d+\.\d+').to_s
end
end
end
end

Some files were not shown because too many files have changed in this diff Show More