forked from rubocop/rubocop
/
raise_exception.rb
91 lines (77 loc) · 2.82 KB
/
raise_exception.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
# frozen_string_literal: true
module RuboCop
module Cop
module Lint
# This cop 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 behaviour.
#
# @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