forked from rubocop/rubocop
-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add new
InternalAffairs/CopDescription
cop
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
Showing
3 changed files
with
220 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
123
spec/rubocop/cop/internal_affairs/cop_description_spec.rb
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |