lib/rubocop/cop/lint/raise_exception.rb
# frozen_string_literal: true
module RuboCop
module Cop
module Lint
# Checks for `raise` or `fail` statements which are
# raising `Exception` class.
#
# You can specify a module name that will be an implicit namespace
# using `AllowedImplicitNamespaces` option. The cop cause a false positive
# for namespaced `Exception` when a namespace is omitted. This option can
# prevent the false positive by specifying a namespace to be omitted for
# `Exception`. Alternatively, make `Exception` a fully qualified class
# name with an explicit namespace.
#
# @safety
# This cop is unsafe because it will change the exception class being
# raised, which is a change in behavior.
#
# @example
# # bad
# raise Exception, 'Error message here'
#
# # good
# raise StandardError, 'Error message here'
#
# @example AllowedImplicitNamespaces: ['Gem']
# # good
# module Gem
# def self.foo
# raise Exception # This exception means `Gem::Exception`.
# end
# end
class RaiseException < Base
extend AutoCorrector
MSG = 'Use `StandardError` over `Exception`.'
RESTRICT_ON_SEND = %i[raise fail].freeze
# @!method exception?(node)
def_node_matcher :exception?, <<~PATTERN
(send nil? {:raise :fail} $(const ${cbase nil?} :Exception) ... )
PATTERN
# @!method exception_new_with_message?(node)
def_node_matcher :exception_new_with_message?, <<~PATTERN
(send nil? {:raise :fail}
(send $(const ${cbase nil?} :Exception) :new ... ))
PATTERN
def on_send(node)
exception?(node, &check(node)) || exception_new_with_message?(node, &check(node))
end
private
def check(node)
lambda do |exception_class, cbase|
return if cbase.nil? && implicit_namespace?(node)
add_offense(exception_class) do |corrector|
prefer_exception = if exception_class.children.first&.cbase_type?
'::StandardError'
else
'StandardError'
end
corrector.replace(exception_class, prefer_exception)
end
end
end
def implicit_namespace?(node)
return false unless (parent = node.parent)
if parent.module_type?
namespace = parent.identifier.source
return allow_implicit_namespaces.include?(namespace)
end
implicit_namespace?(parent)
end
def allow_implicit_namespaces
cop_config['AllowedImplicitNamespaces'] || []
end
end
end
end
end