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
module RuboCop
module Cop
# This cop makes sure that custom error classes, when empty, are declared
# with Class.new.
#
# @example
# # bad
# class FooError < StandardError
# end
#
# # okish
# class FooError < StandardError; end
#
# # good
# FooError = Class.new(StandardError)
class CustomErrorClass < RuboCop::Cop::Cop
MSG = 'Use `Class.new(SuperClass)` to define an empty custom error class.'.freeze
def on_class(node)
_klass, parent, body = node.children
return if body
parent_klass = class_name_from_node(parent)
return unless parent_klass && parent_klass.to_s.end_with?('Error')
add_offense(node, :expression)
end
def autocorrect(node)
klass, parent, _body = node.children
replacement = "#{class_name_from_node(klass)} = Class.new(#{class_name_from_node(parent)})"
lambda do |corrector|
corrector.replace(node.source_range, replacement)
end
end
private
# The nested constant `Foo::Bar::Baz` looks like:
#
# s(:const,
# s(:const,
# s(:const, nil, :Foo), :Bar), :Baz)
#
# So recurse through that to get the name as written in the source.
#
def class_name_from_node(node, suffix = nil)
return unless node&.type == :const
name = node.children[1].to_s
name = "#{name}::#{suffix}" if suffix
if node.children[0]
class_name_from_node(node.children[0], name)
else
name
end
end
end
end
end