Skip to content

Commit

Permalink
Add new Lint/EmptyClass cop
Browse files Browse the repository at this point in the history
  • Loading branch information
fatkodima committed Nov 7, 2020
1 parent 64e9b8c commit 376b4ff
Show file tree
Hide file tree
Showing 7 changed files with 295 additions and 0 deletions.
1 change: 1 addition & 0 deletions changelog/new_empty_class_cop.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* [#9001](https://github.com/rubocop-hq/rubocop/pull/9001): Add new `Lint/EmptyClass` cop. ([@fatkodima][])
6 changes: 6 additions & 0 deletions config/default.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1507,6 +1507,12 @@ Lint/EmptyBlock:
VersionAdded: '1.1'
AllowComments: true

Lint/EmptyClass:
Description: 'Checks for classes and metaclasses without a body.'
Enabled: pending
VersionAdded: '1.3'
AllowComments: false

Lint/EmptyConditionalBody:
Description: 'This cop checks for the presence of `if`, `elsif` and `unless` branches without a body.'
Enabled: true
Expand Down
1 change: 1 addition & 0 deletions docs/modules/ROOT/pages/cops.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,7 @@ In the following section you find all available cops:
* xref:cops_lint.adoc#linteachwithobjectargument[Lint/EachWithObjectArgument]
* xref:cops_lint.adoc#lintelselayout[Lint/ElseLayout]
* xref:cops_lint.adoc#lintemptyblock[Lint/EmptyBlock]
* xref:cops_lint.adoc#lintemptyclass[Lint/EmptyClass]
* xref:cops_lint.adoc#lintemptyconditionalbody[Lint/EmptyConditionalBody]
* xref:cops_lint.adoc#lintemptyensure[Lint/EmptyEnsure]
* xref:cops_lint.adoc#lintemptyexpression[Lint/EmptyExpression]
Expand Down
100 changes: 100 additions & 0 deletions docs/modules/ROOT/pages/cops_lint.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -1183,6 +1183,106 @@ items.each { |item| } # TODO: implement later (inline comment)
| Boolean
|===

== Lint/EmptyClass

|===
| Enabled by default | Safe | Supports autocorrection | VersionAdded | VersionChanged

| Pending
| Yes
| No
| 1.3
| -
|===

This cop checks for classes and metaclasses without a body.
Such empty classes and metaclasses are typically an oversight or we should provide a comment
to be clearer what we're aiming for.

=== Examples

[source,ruby]
----
# bad
class Foo
end
class Bar
class << self
end
end
class << obj
end
# good
class Foo
def do_something
# ... code
end
end
class Bar
class << self
attr_reader :bar
end
end
class << obj
attr_reader :bar
end
----

==== AllowComments: false (default)

[source,ruby]
----
# bad
class Foo
# TODO: implement later
end
class Bar
class << self
# TODO: implement later
end
end
class << obj
# TODO: implement later
end
----

==== AllowComments: true

[source,ruby]
----
# good
class Foo
# TODO: implement later
end
class Bar
class << self
# TODO: implement later
end
end
class << obj
# TODO: implement later
end
----

=== Configurable attributes

|===
| Name | Default value | Configurable values

| AllowComments
| `false`
| Boolean
|===

== Lint/EmptyConditionalBody

|===
Expand Down
1 change: 1 addition & 0 deletions lib/rubocop.rb
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,7 @@
require_relative 'rubocop/cop/lint/each_with_object_argument'
require_relative 'rubocop/cop/lint/else_layout'
require_relative 'rubocop/cop/lint/empty_block'
require_relative 'rubocop/cop/lint/empty_class'
require_relative 'rubocop/cop/lint/empty_conditional_body'
require_relative 'rubocop/cop/lint/empty_ensure'
require_relative 'rubocop/cop/lint/empty_expression'
Expand Down
93 changes: 93 additions & 0 deletions lib/rubocop/cop/lint/empty_class.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
# frozen_string_literal: true

module RuboCop
module Cop
module Lint
# This cop checks for classes and metaclasses without a body.
# Such empty classes and metaclasses are typically an oversight or we should provide a comment
# to be clearer what we're aiming for.
#
# @example
# # bad
# class Foo
# end
#
# class Bar
# class << self
# end
# end
#
# class << obj
# end
#
# # good
# class Foo
# def do_something
# # ... code
# end
# end
#
# class Bar
# class << self
# attr_reader :bar
# end
# end
#
# class << obj
# attr_reader :bar
# end
#
# @example AllowComments: false (default)
# # bad
# class Foo
# # TODO: implement later
# end
#
# class Bar
# class << self
# # TODO: implement later
# end
# end
#
# class << obj
# # TODO: implement later
# end
#
# @example AllowComments: true
# # good
# class Foo
# # TODO: implement later
# end
#
# class Bar
# class << self
# # TODO: implement later
# end
# end
#
# class << obj
# # TODO: implement later
# end
#
class EmptyClass < Base
CLASS_MSG = 'Empty class detected.'
METACLASS_MSG = 'Empty metaclass detected.'

def on_class(node)
add_offense(node, message: CLASS_MSG) unless body_or_allowed_comment_lines?(node) ||
node.parent_class
end

def on_sclass(node)
add_offense(node, message: METACLASS_MSG) unless body_or_allowed_comment_lines?(node)
end

private

def body_or_allowed_comment_lines?(node)
node.body || (cop_config['AllowComments'] && comment_lines?(node))
end
end
end
end
end
93 changes: 93 additions & 0 deletions spec/rubocop/cop/lint/empty_class_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
# frozen_string_literal: true

RSpec.describe RuboCop::Cop::Lint::EmptyClass, :config do
let(:cop_config) do
{ 'AllowComments' => false }
end

it 'registers an offense for empty class' do
expect_offense(<<~RUBY)
class Foo
^^^^^^^^^ Empty class detected.
end
RUBY
end

it 'registers an offense for empty class metaclass' do
expect_offense(<<~RUBY)
class Foo
class << self
^^^^^^^^^^^^^ Empty metaclass detected.
end
end
RUBY
end

it 'registers an offense for empty object metaclass' do
expect_offense(<<~RUBY)
class << obj
^^^^^^^^^^^^ Empty metaclass detected.
end
RUBY
end

it 'registers an offense when empty metaclass contains only comments' do
expect_offense(<<~RUBY)
class Foo
class << self
^^^^^^^^^^^^^ Empty metaclass detected.
# Comment.
end
end
RUBY
end

it 'does not register an offense when class is not empty' do
expect_no_offenses(<<~RUBY)
class Foo
attr_reader :bar
end
RUBY
end

it 'does not register an offense when empty has a parent' do
expect_no_offenses(<<~RUBY)
class Child < Parent
end
RUBY
end

it 'does not register an offense when metaclass is not empty' do
expect_no_offenses(<<~RUBY)
class Foo
class << self
attr_reader :bar
end
end
RUBY
end

context 'when AllowComments is true' do
let(:cop_config) do
{ 'AllowComments' => true }
end

it 'does not register an offense when empty class contains only comments' do
expect_no_offenses(<<~RUBY)
class Foo
# Comment.
end
RUBY
end

it 'does not register an offense when empty metaclass contains only comments' do
expect_no_offenses(<<~RUBY)
class Foo
class << self
# Comment.
end
end
RUBY
end
end
end

0 comments on commit 376b4ff

Please sign in to comment.