Skip to content

Commit

Permalink
Add new InternalAffairs/CopDescription cop
Browse files Browse the repository at this point in the history
Enforces the cop discription to start with a word such as verb.

## example

```rb
# bad
# This cop checks ....
class SomeCop < Base
  ....
end

# good
# Checks ...
class SomeCop < Base
  ...
end
```
  • Loading branch information
nobuyo authored and bbatsov committed May 13, 2022
1 parent 277fa14 commit 4448a88
Show file tree
Hide file tree
Showing 3 changed files with 220 additions and 0 deletions.
1 change: 1 addition & 0 deletions lib/rubocop/cop/internal_affairs.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# frozen_string_literal: true

require_relative 'internal_affairs/cop_description'
require_relative 'internal_affairs/empty_line_between_expect_offense_and_correction'
require_relative 'internal_affairs/example_description'
require_relative 'internal_affairs/inherit_deprecated_cop_class'
Expand Down
96 changes: 96 additions & 0 deletions lib/rubocop/cop/internal_affairs/cop_description.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
# frozen_string_literal: true

module RuboCop
module Cop
module InternalAffairs
# Enforces the cop description to start with a word such as verb.
#
# @example
# # bad
# # This cop checks ....
# class SomeCop < Base
# ....
# end
#
# # good
# # Checks ...
# class SomeCop < Base
# ...
# end
#
class CopDescription < Base
extend AutoCorrector

MSG = 'Description should be started with %<suggestion>s instead of `This cop ...`.'

SPECIAL_WORDS = %w[is can could should will would must may].freeze
COP_DESC_OFFENSE_REGEX = \
/^\s+# This cop (?<special>#{SPECIAL_WORDS.join('|')})?\s*(?<word>.+?) .*/.freeze
REPLACEMENT_REGEX = /^\s+# This cop (#{SPECIAL_WORDS.join('|')})?\s*(.+?) /.freeze

def on_class(node)
return unless (module_node = node.parent)

description_beginning = first_comment_line(module_node)
return unless description_beginning

start_with_subject = description_beginning.match(COP_DESC_OFFENSE_REGEX)
return unless start_with_subject

suggestion = start_with_subject['word']&.capitalize
range = range(module_node, description_beginning)
suggestion_for_message = suggestion_for_message(suggestion, start_with_subject)
message = format(MSG, suggestion: suggestion_for_message)

add_offense(range, message: message) do |corrector|
if suggestion && !start_with_subject['special']
replace_with_suggestion(corrector, range, suggestion, description_beginning)
end
end
end

private

def replace_with_suggestion(corrector, range, suggestion, description_beginning)
replacement = description_beginning.gsub(REPLACEMENT_REGEX, "#{suggestion} ")
corrector.replace(range, replacement)
end

def range(node, comment_line)
source_buffer = node.loc.expression.source_buffer

begin_pos = node.loc.expression.begin_pos
begin_pos += comment_index(node, comment_line)
end_pos = begin_pos + comment_body(comment_line).length

Parser::Source::Range.new(source_buffer, begin_pos, end_pos)
end

def suggestion_for_message(suggestion, match_data)
if suggestion && !match_data['special']
"`#{suggestion}`"
else
'a word such as verb'
end
end

def first_comment_line(node)
node.loc.expression.source.lines.find { |line| comment_line?(line) }
end

def comment_body(comment_line)
comment_line.gsub(/^\s*# /, '')
end

def comment_index(node, comment_line)
body = comment_body(comment_line)
node.loc.expression.source.index(body)
end

def relevant_file?(file)
file.match?(%r{/cop/.*\.rb\z})
end
end
end
end
end
123 changes: 123 additions & 0 deletions spec/rubocop/cop/internal_affairs/cop_description_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
# frozen_string_literal: true

RSpec.describe RuboCop::Cop::InternalAffairs::CopDescription, :config do
before do
allow_any_instance_of(described_class).to receive(:relevant_file?).and_return(true) # rubocop:disable RSpec/AnyInstance
end

context 'The description starts with `This cop ...`' do
it 'registers an offense and corrects if using just a verb' do
expect_offense(<<~RUBY)
module RuboCop
module Cop
module Lint
# This cop checks some offenses...
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Description should be started with `Checks` instead of `This cop ...`.
#
# ...
class Foo < Base
end
end
end
end
RUBY

expect_correction(<<~RUBY)
module RuboCop
module Cop
module Lint
# Checks some offenses...
#
# ...
class Foo < Base
end
end
end
end
RUBY
end

it 'registers an offense if using an auxiliary verb' do
expect_offense(<<~RUBY)
module RuboCop
module Cop
module Lint
# This cop can check some offenses...
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Description should be started with a word such as verb instead of `This cop ...`.
#
# ...
class Foo < Base
end
end
end
end
RUBY
end

it 'registers an offense if the description like `This cop is ...`' do
expect_offense(<<~RUBY)
module RuboCop
module Cop
module Lint
# This cop is used to...
^^^^^^^^^^^^^^^^^^^^^^ Description should be started with a word such as verb instead of `This cop ...`.
#
# ...
class Foo < Base
end
end
end
end
RUBY
end
end

context 'The description starts with a word such as verb' do
it 'does not register if the description like `Checks`' do
expect_no_offenses(<<~RUBY)
module RuboCop
module Cop
module Lint
# Checks some problem
#
# ...
class Foo < Base
end
end
end
end
RUBY
end

it 'does not register if the description starts with non-verb word' do
expect_no_offenses(<<~RUBY)
module RuboCop
module Cop
module Lint
# Either foo or bar ...
#
# ...
class Foo < Base
end
end
end
end
RUBY
end
end

context 'There is no description comment' do
it 'does not register offense' do
expect_no_offenses(<<~RUBY)
module RuboCop
module Cop
module Lint
class Foo < Base
end
end
end
end
RUBY
end
end
end

0 comments on commit 4448a88

Please sign in to comment.