/
inherit_exception.rb
110 lines (96 loc) · 2.93 KB
/
inherit_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
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
# frozen_string_literal: true
module RuboCop
module Cop
module Lint
# This cop looks for error classes inheriting from `Exception`
# and its standard library subclasses, excluding subclasses of
# `StandardError`. It is configurable to suggest using either
# `RuntimeError` (default) or `StandardError` instead.
#
# @safety
# This cop's autocorrection is unsafe because `rescue` that omit
# exception class handle `StandardError` and its subclasses,
# but not `Exception` and its subclasses.
#
# @example EnforcedStyle: runtime_error (default)
# # bad
#
# class C < Exception; end
#
# C = Class.new(Exception)
#
# # good
#
# class C < RuntimeError; end
#
# C = Class.new(RuntimeError)
#
# @example EnforcedStyle: standard_error
# # bad
#
# class C < Exception; end
#
# C = Class.new(Exception)
#
# # good
#
# class C < StandardError; end
#
# C = Class.new(StandardError)
class InheritException < Base
include ConfigurableEnforcedStyle
extend AutoCorrector
MSG = 'Inherit from `%<prefer>s` instead of `%<current>s`.'
PREFERRED_BASE_CLASS = {
runtime_error: 'RuntimeError',
standard_error: 'StandardError'
}.freeze
ILLEGAL_CLASSES = %w[
Exception
SystemStackError
NoMemoryError
SecurityError
NotImplementedError
LoadError
SyntaxError
ScriptError
Interrupt
SignalException
SystemExit
].freeze
RESTRICT_ON_SEND = %i[new].freeze
# @!method class_new_call?(node)
def_node_matcher :class_new_call?, <<~PATTERN
(send
(const {cbase nil?} :Class) :new
$(const {cbase nil?} _))
PATTERN
def on_class(node)
return unless node.parent_class && illegal_class_name?(node.parent_class)
message = message(node.parent_class)
add_offense(node.parent_class, message: message) do |corrector|
corrector.replace(node.parent_class, preferred_base_class)
end
end
def on_send(node)
constant = class_new_call?(node)
return unless constant && illegal_class_name?(constant)
message = message(constant)
add_offense(constant, message: message) do |corrector|
corrector.replace(constant, preferred_base_class)
end
end
private
def message(node)
format(MSG, prefer: preferred_base_class, current: node.const_name)
end
def illegal_class_name?(class_node)
ILLEGAL_CLASSES.include?(class_node.const_name)
end
def preferred_base_class
PREFERRED_BASE_CLASS[style]
end
end
end
end
end