diff --git a/CHANGELOG.md b/CHANGELOG.md index 8b73d5963bc..5b8b41c3dc0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5722,3 +5722,4 @@ [@timlkelly]: https://github.com/timlkelly [@AirWick219]: https://github.com/AirWick219 [@markburns]: https://github.com/markburns +[@gregfletch]: https://github.com/gregfletch diff --git a/changelog/fix_make_comment_annotation_style_configurable.md b/changelog/fix_make_comment_annotation_style_configurable.md new file mode 100644 index 00000000000..64b4924488a --- /dev/null +++ b/changelog/fix_make_comment_annotation_style_configurable.md @@ -0,0 +1 @@ +* [#9890](https://github.com/rubocop/rubocop/issues/9890): Make colon after comment annotation configurable. ([@gregfletch][]) diff --git a/config/default.yml b/config/default.yml index 3cf7c7ea2ae..11af268c520 100644 --- a/config/default.yml +++ b/config/default.yml @@ -3150,6 +3150,7 @@ Style/CommentAnnotation: - HACK - REVIEW - NOTE + RequireColon: true Style/CommentedKeyword: Description: 'Do not place comments on the same line as certain keywords.' diff --git a/lib/rubocop/cop/style/comment_annotation.rb b/lib/rubocop/cop/style/comment_annotation.rb index fb62a145aa5..24f7b092a0a 100644 --- a/lib/rubocop/cop/style/comment_annotation.rb +++ b/lib/rubocop/cop/style/comment_annotation.rb @@ -12,7 +12,7 @@ module Style # incorrect registering of keywords (eg. `review`) inside a paragraph as an # annotation. # - # @example + # @example RequireColon: true (default) # # bad # # TODO make better # @@ -36,14 +36,36 @@ module Style # # # good # # OPTIMIZE: does not work + # + # @example RequireColon: false + # # bad + # # TODO: make better + # + # # good + # # TODO make better + # + # # bad + # # fixme does not work + # + # # good + # # FIXME does not work + # + # # bad + # # Optimize does not work + # + # # good + # # OPTIMIZE does not work class CommentAnnotation < Base include AnnotationComment include RangeHelp extend AutoCorrector - MSG = 'Annotation keywords like `%s` should be all ' \ - 'upper case, followed by a colon, and a space, ' \ - 'then a note describing the problem.' + MSG_COLON_STYLE = 'Annotation keywords like `%s` should be all ' \ + 'upper case, followed by a colon, and a space, ' \ + 'then a note describing the problem.' + MSG_SPACE_STYLE = 'Annotation keywords like `%s` should be all ' \ + 'upper case, followed by a space, ' \ + 'then a note describing the problem.' MISSING_NOTE = 'Annotation comment, with keyword `%s`, is missing a note.' def on_new_investigation @@ -63,13 +85,15 @@ def on_new_investigation private def register_offense(range, note, first_word) + message = requires_colon? ? MSG_COLON_STYLE : MSG_SPACE_STYLE + add_offense( range, - message: format(note ? MSG : MISSING_NOTE, keyword: first_word) + message: format(note ? message : MISSING_NOTE, keyword: first_word) ) do |corrector| next if note.nil? - corrector.replace(range, "#{first_word.upcase}: ") + correct_offense(corrector, range, first_word) end end @@ -92,8 +116,28 @@ def concat_length(*args) end def correct_annotation?(first_word, colon, space, note) + return correct_colon_annotation?(first_word, colon, space, note) if requires_colon? + + correct_space_annotation?(first_word, colon, space, note) + end + + def correct_colon_annotation?(first_word, colon, space, note) keyword?(first_word) && (colon && space && note || !colon && !note) end + + def correct_space_annotation?(first_word, colon, space, note) + keyword?(first_word) && (!colon && space && note || !colon && !note) + end + + def correct_offense(corrector, range, first_word) + return corrector.replace(range, "#{first_word.upcase}: ") if requires_colon? + + corrector.replace(range, "#{first_word.upcase} ") + end + + def requires_colon? + cop_config['RequireColon'] + end end end end diff --git a/spec/rubocop/cop/style/comment_annotation_spec.rb b/spec/rubocop/cop/style/comment_annotation_spec.rb index 43ae1794e84..6dd3b57a464 100644 --- a/spec/rubocop/cop/style/comment_annotation_spec.rb +++ b/spec/rubocop/cop/style/comment_annotation_spec.rb @@ -1,144 +1,273 @@ # frozen_string_literal: true RSpec.describe RuboCop::Cop::Style::CommentAnnotation, :config do - let(:cop_config) { { 'Keywords' => %w[TODO FIXME OPTIMIZE HACK REVIEW] } } + context 'with default RequireColon configuration (colon + space)' do + let(:cop_config) { { 'Keywords' => %w[TODO FIXME OPTIMIZE HACK REVIEW] } } - context 'missing colon' do - it 'registers an offense and adds colon' do - expect_offense(<<~RUBY) - # TODO make better - ^^^^^ Annotation keywords like `TODO` should be all upper case, followed by a colon, and a space, then a note describing the problem. - RUBY + context 'missing colon' do + it 'registers an offense and adds colon' do + expect_offense(<<~RUBY) + # TODO make better + ^^^^^ Annotation keywords like `TODO` should be all upper case, followed by a colon, and a space, then a note describing the problem. + RUBY - expect_correction(<<~RUBY) - # TODO: make better - RUBY + expect_correction(<<~RUBY) + # TODO: make better + RUBY + end end - end - context 'with configured keyword' do - let(:cop_config) { { 'Keywords' => %w[ISSUE] } } + context 'with configured keyword' do + let(:cop_config) { { 'Keywords' => %w[ISSUE] } } - it 'registers an offense for a missing colon after the word' do - expect_offense(<<~RUBY) - # ISSUE wrong order - ^^^^^^ Annotation keywords like `ISSUE` should be all upper case, followed by a colon, and a space, then a note describing the problem. - RUBY + it 'registers an offense for a missing colon after the word' do + expect_offense(<<~RUBY) + # ISSUE wrong order + ^^^^^^ Annotation keywords like `ISSUE` should be all upper case, followed by a colon, and a space, then a note describing the problem. + RUBY - expect_correction(<<~RUBY) - # ISSUE: wrong order - RUBY + expect_correction(<<~RUBY) + # ISSUE: wrong order + RUBY + end end - end - context 'missing space after colon' do - it 'registers an offense and adds space' do - expect_offense(<<~RUBY) - # TODO:make better - ^^^^^ Annotation keywords like `TODO` should be all upper case, followed by a colon, and a space, then a note describing the problem. - RUBY + context 'missing space after colon' do + it 'registers an offense and adds space' do + expect_offense(<<~RUBY) + # TODO:make better + ^^^^^ Annotation keywords like `TODO` should be all upper case, followed by a colon, and a space, then a note describing the problem. + RUBY - expect_correction(<<~RUBY) - # TODO: make better - RUBY + expect_correction(<<~RUBY) + # TODO: make better + RUBY + end end - end - context 'lower case keyword' do - it 'registers an offense and upcases' do - expect_offense(<<~RUBY) - # fixme: does not work - ^^^^^^^ Annotation keywords like `fixme` should be all upper case, followed by a colon, and a space, then a note describing the problem. - RUBY + context 'lower case keyword' do + it 'registers an offense and upcases' do + expect_offense(<<~RUBY) + # fixme: does not work + ^^^^^^^ Annotation keywords like `fixme` should be all upper case, followed by a colon, and a space, then a note describing the problem. + RUBY - expect_correction(<<~RUBY) - # FIXME: does not work - RUBY + expect_correction(<<~RUBY) + # FIXME: does not work + RUBY + end end - end - context 'capitalized keyword' do - it 'registers an offense and upcases' do - expect_offense(<<~RUBY) - # Optimize: does not work - ^^^^^^^^^^ Annotation keywords like `Optimize` should be all upper case, followed by a colon, and a space, then a note describing the problem. - RUBY + context 'capitalized keyword' do + it 'registers an offense and upcases' do + expect_offense(<<~RUBY) + # Optimize: does not work + ^^^^^^^^^^ Annotation keywords like `Optimize` should be all upper case, followed by a colon, and a space, then a note describing the problem. + RUBY + + expect_correction(<<~RUBY) + # OPTIMIZE: does not work + RUBY + end + end - expect_correction(<<~RUBY) - # OPTIMIZE: does not work + context 'upper case keyword with colon by no note' do + it 'registers an offense without auto-correction' do + expect_offense(<<~RUBY) + # HACK: + ^^^^^ Annotation comment, with keyword `HACK`, is missing a note. + RUBY + + expect_no_corrections + end + end + + it 'accepts upper case keyword with colon, space and note' do + expect_no_offenses('# REVIEW: not sure about this') + end + + it 'accepts upper case keyword alone' do + expect_no_offenses('# OPTIMIZE') + end + + it 'accepts a comment that is obviously a code example' do + expect_no_offenses('# Todo.destroy(1)') + end + + it 'accepts a keyword that is just the beginning of a sentence' do + expect_no_offenses(<<~RUBY) + # Optimize if you want. I wouldn't recommend it. + # Hack is a fun game. RUBY end - end - context 'upper case keyword with colon by no note' do - it 'registers an offense without auto-correction' do - expect_offense(<<~RUBY) - # HACK: - ^^^^^ Annotation comment, with keyword `HACK`, is missing a note. + it 'accepts a keyword that is somewhere in a sentence' do + expect_no_offenses(<<~RUBY) + # Example: There are three reviews, with ranks 1, 2, and 3. A new + # review is saved with rank 2. The two reviews that originally had + # ranks 2 and 3 will have their ranks increased to 3 and 4. RUBY + end - expect_no_corrections + context 'when a keyword is not in the configuration' do + let(:cop_config) { { 'Keywords' => %w[FIXME OPTIMIZE HACK REVIEW] } } + + it 'accepts the word without colon' do + expect_no_offenses('# TODO make better') + end end - end - it 'accepts upper case keyword with colon, space and note' do - expect_no_offenses('# REVIEW: not sure about this') - end + context 'offenses in consecutive inline comments' do + it 'registers each of them' do + expect_offense(<<~RUBY) + class ToBeDone + ITEMS = [ + '', # TODO Item 1 + ^^^^^ Annotation keywords like `TODO` should be all upper case, followed by a colon, and a space, then a note describing the problem. + '', # TODO Item 2 + ^^^^^ Annotation keywords like `TODO` should be all upper case, followed by a colon, and a space, then a note describing the problem. + ].freeze + end + RUBY + end + end - it 'accepts upper case keyword alone' do - expect_no_offenses('# OPTIMIZE') + context 'multiline comment' do + it 'only registers an offense on the first line' do + expect_offense(<<~RUBY) + # TODO line 1 + ^^^^^ Annotation keywords like `TODO` should be all upper case, followed by a colon, and a space, then a note describing the problem. + # TODO line 2 + # TODO line 3 + RUBY + end + end end - it 'accepts a comment that is obviously a code example' do - expect_no_offenses('# Todo.destroy(1)') - end + context 'with RequireColon configuration set to false' do + let(:cop_config) do + { + 'Keywords' => %w[TODO FIXME OPTIMIZE HACK REVIEW], + 'RequireColon' => false + } + end - it 'accepts a keyword that is just the beginning of a sentence' do - expect_no_offenses(<<~RUBY) - # Optimize if you want. I wouldn't recommend it. - # Hack is a fun game. - RUBY - end + context 'with colon' do + it 'registers an offense and removes colon' do + expect_offense(<<~RUBY) + # TODO: make better + ^^^^^^ Annotation keywords like `TODO` should be all upper case, followed by a space, then a note describing the problem. + RUBY - it 'accepts a keyword that is somewhere in a sentence' do - expect_no_offenses(<<~RUBY) - # Example: There are three reviews, with ranks 1, 2, and 3. A new - # review is saved with rank 2. The two reviews that originally had - # ranks 2 and 3 will have their ranks increased to 3 and 4. - RUBY - end + expect_correction(<<~RUBY) + # TODO make better + RUBY + end + end + + context 'with configured keyword' do + let(:cop_config) { { 'Keywords' => %w[ISSUE], 'RequireColon' => false } } - context 'when a keyword is not in the configuration' do - let(:cop_config) { { 'Keywords' => %w[FIXME OPTIMIZE HACK REVIEW] } } + it 'registers an offense for containing a colon after the word' do + expect_offense(<<~RUBY) + # ISSUE: wrong order + ^^^^^^^ Annotation keywords like `ISSUE` should be all upper case, followed by a space, then a note describing the problem. + RUBY - it 'accepts the word without colon' do - expect_no_offenses('# TODO make better') + expect_correction(<<~RUBY) + # ISSUE wrong order + RUBY + end + end + + context 'lower case keyword' do + it 'registers an offense and upcases' do + expect_offense(<<~RUBY) + # fixme does not work + ^^^^^^ Annotation keywords like `fixme` should be all upper case, followed by a space, then a note describing the problem. + RUBY + + expect_correction(<<~RUBY) + # FIXME does not work + RUBY + end + end + + context 'upper case keyword with colon by no note' do + it 'registers an offense without auto-correction' do + expect_offense(<<~RUBY) + # HACK: + ^^^^^ Annotation comment, with keyword `HACK`, is missing a note. + RUBY + + expect_no_corrections + end end - end - context 'offenses in consecutive inline comments' do - it 'registers each of them' do - expect_offense(<<~RUBY) - class ToBeDone - ITEMS = [ - '', # TODO Item 1 - ^^^^^ Annotation keywords like `TODO` should be all upper case, followed by a colon, and a space, then a note describing the problem. - '', # TODO Item 2 - ^^^^^ Annotation keywords like `TODO` should be all upper case, followed by a colon, and a space, then a note describing the problem. - ].freeze - end + it 'accepts upper case keyword with colon, space and note' do + expect_no_offenses('# REVIEW not sure about this') + end + + it 'accepts upper case keyword alone' do + expect_no_offenses('# OPTIMIZE') + end + + it 'accepts a comment that is obviously a code example' do + expect_no_offenses('# Todo.destroy(1)') + end + + it 'accepts a keyword that is just the beginning of a sentence' do + expect_no_offenses(<<~RUBY) + # Optimize if you want. I wouldn't recommend it. + # Hack is a fun game. RUBY end - end - context 'multiline comment' do - it 'only registers an offense on the first line' do - expect_offense(<<~RUBY) - # TODO line 1 - ^^^^^ Annotation keywords like `TODO` should be all upper case, followed by a colon, and a space, then a note describing the problem. - # TODO line 2 - # TODO line 3 + it 'accepts a keyword that is somewhere in a sentence' do + expect_no_offenses(<<~RUBY) + # Example: There are three reviews, with ranks 1, 2, and 3. A new + # review is saved with rank 2. The two reviews that originally had + # ranks 2 and 3 will have their ranks increased to 3 and 4. RUBY end + + context 'when a keyword is not in the configuration' do + let(:cop_config) do + { + 'Keywords' => %w[FIXME OPTIMIZE HACK REVIEW], + 'RequireColon' => false + } + end + + it 'accepts the word with colon' do + expect_no_offenses('# TODO: make better') + end + end + + context 'offenses in consecutive inline comments' do + it 'registers each of them' do + expect_offense(<<~RUBY) + class ToBeDone + ITEMS = [ + '', # TODO: Item 1 + ^^^^^^ Annotation keywords like `TODO` should be all upper case, followed by a space, then a note describing the problem. + '', # TODO: Item 2 + ^^^^^^ Annotation keywords like `TODO` should be all upper case, followed by a space, then a note describing the problem. + ].freeze + end + RUBY + end + end + + context 'multiline comment' do + it 'only registers an offense on the first line' do + expect_offense(<<~RUBY) + # TODO: line 1 + ^^^^^^ Annotation keywords like `TODO` should be all upper case, followed by a space, then a note describing the problem. + # TODO: line 2 + # TODO: line 3 + RUBY + end + end end end