Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add new Lint/EmptyClass cop #9001

Merged
merged 1 commit into from
Nov 8, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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