diff options
author | Sean McGivern <sean@gitlab.com> | 2017-02-28 10:41:59 +0000 |
---|---|---|
committer | Sean McGivern <sean@gitlab.com> | 2017-03-01 10:53:10 +0000 |
commit | 8dd097a91530e4b047c4b391f21047c7d29d310d (patch) | |
tree | e05f69a73b248d88c6dd61c61808f84d6b82b9af /rubocop | |
parent | 6b4d490782a7b0c2a4a7f7eb6ed5ef2c25fc1c4d (diff) | |
download | gitlab-ce-8dd097a91530e4b047c4b391f21047c7d29d310d.tar.gz |
Add RuboCop cop for custom error classes
From the Ruby style guide:
# bad
class FooError < StandardError
end
# okish
class FooError < StandardError; end
# good
FooError = Class.new(StandardError)
This cop does that, but only for error classes (classes where the
superclass ends in 'Error'). We have empty controllers and models, which
are perfectly valid empty classes.
Diffstat (limited to 'rubocop')
-rw-r--r-- | rubocop/cop/custom_error_class.rb | 64 |
1 files changed, 64 insertions, 0 deletions
diff --git a/rubocop/cop/custom_error_class.rb b/rubocop/cop/custom_error_class.rb new file mode 100644 index 00000000000..38d93acfe88 --- /dev/null +++ b/rubocop/cop/custom_error_class.rb @@ -0,0 +1,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 |