diff --git a/CHANGELOG.md b/CHANGELOG.md index 4b5d0661ea4..c74292b2a96 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ * [#7823](https://github.com/rubocop-hq/rubocop/pull/7823): Add `IgnoredMethods` configuration in `Metrics/AbcSize`, `Metrics/CyclomaticComplexity`, and `Metrics/PerceivedComplexity` cops. ([@drenmi][]) * [#7816](https://github.com/rubocop-hq/rubocop/pull/7816): Support Ruby 2.7's numbered parameter for `Style/Lambda`. ([@koic][]) * [#7829](https://github.com/rubocop-hq/rubocop/issues/7829): Fix an error for `Style/OneLineConditional` when one of the branches contains `next` keyword. ([@koic][]) +* [#7384](https://github.com/rubocop-hq/rubocop/pull/7384): Add new `Style/DisableCopsWithinSourceCodeDirective` cop. ([@egze][]) ### Bug fixes @@ -4424,3 +4425,4 @@ [@nikitasakov]: https://github.com/nikitasakov [@dmolesUC]: https://github.com/dmolesUC [@yuritomanek]: https://github.com/yuritomanek +[@egze]: https://github.com/egze diff --git a/config/default.yml b/config/default.yml index e0a4f83a298..3544924dfb8 100644 --- a/config/default.yml +++ b/config/default.yml @@ -2616,6 +2616,12 @@ Style/Dir: Enabled: true VersionAdded: '0.50' +Style/DisableCopsWithinSourceCodeDirective: + Description: >- + Forbids disabling/enabling cops within source code. + Enabled: false + VersionAdded: '0.75' + Style/Documentation: Description: 'Document classes and non-namespace modules.' Enabled: true diff --git a/lib/rubocop.rb b/lib/rubocop.rb index d9f4d8d6706..60717410ba9 100644 --- a/lib/rubocop.rb +++ b/lib/rubocop.rb @@ -423,6 +423,7 @@ require_relative 'rubocop/cop/style/date_time' require_relative 'rubocop/cop/style/def_with_parentheses' require_relative 'rubocop/cop/style/dir' +require_relative 'rubocop/cop/style/disable_cops_within_source_code_directive' require_relative 'rubocop/cop/style/documentation_method' require_relative 'rubocop/cop/style/documentation' require_relative 'rubocop/cop/style/double_cop_disable_directive' @@ -585,7 +586,6 @@ # relies on simple text require_relative 'rubocop/formatter/clang_style_formatter' require_relative 'rubocop/formatter/disabled_config_formatter' -require_relative 'rubocop/formatter/disabled_lines_formatter' require_relative 'rubocop/formatter/emacs_style_formatter' require_relative 'rubocop/formatter/file_list_formatter' require_relative 'rubocop/formatter/fuubar_style_formatter' diff --git a/lib/rubocop/cop/style/disable_cops_within_source_code_directive.rb b/lib/rubocop/cop/style/disable_cops_within_source_code_directive.rb new file mode 100644 index 00000000000..55fb434c0a1 --- /dev/null +++ b/lib/rubocop/cop/style/disable_cops_within_source_code_directive.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +# rubocop:disable Lint/RedundantCopDisableDirective + +module RuboCop + module Cop + module Style + # Detects comments to enable/disable RuboCop. + # This is useful if want to make sure that every RuboCop error gets fixed + # and not quickly disabled with a comment. + # + # @example + # # bad + # # rubocop:disable Metrics/AbcSize + # def f + # end + # # rubocop:enable Metrics/AbcSize + # + # # good + # def fixed_method_name_and_no_rubocop_comments + # end + # + class DisableCopsWithinSourceCodeDirective < Cop + # rubocop:enable Lint/RedundantCopDisableDirective + MSG = 'Comment to disable/enable RuboCop.' + + def investigate(processed_source) + processed_source.comments.each do |comment| + next unless rubocop_directive_comment?(comment) + + add_offense(comment) + end + end + + def autocorrect(comment) + lambda do |corrector| + corrector.replace(comment.loc.expression, '') + end + end + + private + + def rubocop_directive_comment?(comment) + comment.text =~ CommentConfig::COMMENT_DIRECTIVE_REGEXP + end + end + end + end +end diff --git a/lib/rubocop/formatter/disabled_lines_formatter.rb b/lib/rubocop/formatter/disabled_lines_formatter.rb deleted file mode 100644 index 5e9e0017247..00000000000 --- a/lib/rubocop/formatter/disabled_lines_formatter.rb +++ /dev/null @@ -1,57 +0,0 @@ -# frozen_string_literal: true - -module RuboCop - module Formatter - # A basic formatter that displays the lines disabled - # inline comments. - class DisabledLinesFormatter < BaseFormatter - include PathUtil - include Colorizable - - attr_reader :cop_disabled_line_ranges - - def started(_target_files) - @cop_disabled_line_ranges = {} - end - - def file_started(file, options) - return unless options[:cop_disabled_line_ranges] - - @cop_disabled_line_ranges[file] = - options[:cop_disabled_line_ranges] - end - - def finished(_inspected_files) - cops_disabled_in_comments_summary - end - - private - - def cops_disabled_in_comments_summary - summary = "\nCops disabled line ranges:\n\n" - - @cop_disabled_line_ranges.each do |file, disabled_cops| - disabled_cops.each do |cop, line_ranges| - line_ranges.each do |line_range| - file = cyan(smart_path(file)) - summary += "#{file}:#{line_range}: #{cop}\n" - end - end - end - - output.puts summary - end - - def smart_path(path) - # Ideally, we calculate this relative to the project root. - base_dir = Dir.pwd - - if path.start_with? base_dir - relative_path(path, base_dir) - else - path - end - end - end - end -end diff --git a/lib/rubocop/formatter/formatter_set.rb b/lib/rubocop/formatter/formatter_set.rb index 08e83de3357..f85bf17acd3 100644 --- a/lib/rubocop/formatter/formatter_set.rb +++ b/lib/rubocop/formatter/formatter_set.rb @@ -11,7 +11,6 @@ class FormatterSet < Array BUILTIN_FORMATTERS_FOR_KEYS = { '[a]utogenconf' => AutoGenConfigFormatter, '[c]lang' => ClangStyleFormatter, - '[d]isabled' => DisabledLinesFormatter, '[e]macs' => EmacsStyleFormatter, '[fi]les' => FileListFormatter, '[fu]ubar' => FuubarStyleFormatter, diff --git a/manual/cops.md b/manual/cops.md index ae10d5af5d6..aafcfc2968e 100644 --- a/manual/cops.md +++ b/manual/cops.md @@ -334,6 +334,7 @@ In the following section you find all available cops: * [Style/DateTime](cops_style.md#styledatetime) * [Style/DefWithParentheses](cops_style.md#styledefwithparentheses) * [Style/Dir](cops_style.md#styledir) +* [Style/DisableCopsWithinSourceCodeDirective](cops_style.md#styledisablecopswithinsourcecodedirective) * [Style/Documentation](cops_style.md#styledocumentation) * [Style/DocumentationMethod](cops_style.md#styledocumentationmethod) * [Style/DoubleCopDisableDirective](cops_style.md#styledoublecopdisabledirective) diff --git a/manual/cops_style.md b/manual/cops_style.md index ea135748be3..856d78d29c8 100644 --- a/manual/cops_style.md +++ b/manual/cops_style.md @@ -1334,6 +1334,30 @@ path = File.dirname(File.realpath(__FILE__)) path = __dir__ ``` +## Style/DisableCopsWithinSourceCodeDirective + +Enabled by default | Safe | Supports autocorrection | VersionAdded | VersionChanged +--- | --- | --- | --- | --- +Disabled | Yes | Yes | 0.75 | - + +Detects comments to enable/disable RuboCop. +This is useful if want to make sure that every RuboCop error gets fixed +and not quickly disabled with a comment. + +### Examples + +```ruby +# bad +# rubocop:disable Metrics/AbcSize +def f +end +# rubocop:enable Metrics/AbcSize + +# good +def fixed_method_name_and_no_rubocop_comments +end +``` + ## Style/Documentation Enabled by default | Safe | Supports autocorrection | VersionAdded | VersionChanged diff --git a/spec/rubocop/cop/style/disable_cops_within_source_code_directive_spec.rb b/spec/rubocop/cop/style/disable_cops_within_source_code_directive_spec.rb new file mode 100644 index 00000000000..19eb00b4326 --- /dev/null +++ b/spec/rubocop/cop/style/disable_cops_within_source_code_directive_spec.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +RSpec.describe RuboCop::Cop::Style::DisableCopsWithinSourceCodeDirective do + subject(:cop) { described_class.new } + + it 'registers an offense for disabled cop within source code' do + expect_offense(<<~RUBY) + def choose_move(who_to_move)# rubocop:disable Metrics/CyclomaticComplexity + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Comment to disable/enable RuboCop. + end + RUBY + expect_correction(<<~RUBY) + def choose_move(who_to_move) + end + RUBY + end + + it 'registers an offense for enabled cop within source code' do + expect_offense(<<~RUBY) + def choose_move(who_to_move)# rubocop:enable Metrics/CyclomaticComplexity + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Comment to disable/enable RuboCop. + end + RUBY + expect_correction(<<~RUBY) + def choose_move(who_to_move) + end + RUBY + end +end diff --git a/spec/rubocop/formatter/disabled_lines_formatter_spec.rb b/spec/rubocop/formatter/disabled_lines_formatter_spec.rb deleted file mode 100644 index d10c01c3349..00000000000 --- a/spec/rubocop/formatter/disabled_lines_formatter_spec.rb +++ /dev/null @@ -1,83 +0,0 @@ -# frozen_string_literal: true - -RSpec.describe RuboCop::Formatter::DisabledLinesFormatter do - subject(:formatter) { described_class.new(output) } - - let(:output) { StringIO.new } - - let(:files) do - %w[lib/rubocop.rb spec/spec_helper.rb exe/rubocop].map do |path| - File.expand_path(path) - end - end - - let(:file_started) do - formatter.file_started(files.first, cop_disabled_line_ranges) - end - - describe '#file_started' do - before { formatter.started(files) } - - context 'when no disable cop comments are detected' do - let(:cop_disabled_line_ranges) { {} } - - it 'does not add to cop_disabled_line_ranges' do - expect { file_started }.not_to( - change(formatter, :cop_disabled_line_ranges) - ) - end - end - - context 'when any disable cop comments are detected' do - let(:cop_disabled_line_ranges) do - { cop_disabled_line_ranges: { 'LineLength' => [1..1] } } - end - - it 'merges the changes into cop_disabled_line_ranges' do - expect { file_started }.to( - change(formatter, :cop_disabled_line_ranges) - ) - end - end - end - - describe '#finished' do - context 'when there disabled cops detected' do - let(:cop_disabled_line_ranges) do - { - cop_disabled_line_ranges: { - 'LineLength' => [1..1], - 'ClassLength' => [1..Float::INFINITY] - } - } - end - let(:offenses) do - [ - RuboCop::Cop::Offense.new(:convention, location, 'Class too long.', - 'ClassLength', :disabled), - RuboCop::Cop::Offense.new(:convention, location, 'Line too long.', - 'LineLength', :uncorrected) - ] - end - let(:location) { OpenStruct.new(line: 3) } - - before do - formatter.started(files) - formatter.file_started('lib/rubocop.rb', cop_disabled_line_ranges) - formatter.file_finished('lib/rubocop.rb', offenses) - end - - it 'lists disabled cops by file' do - formatter.finished(files) - expect(output.string) - .to eq(<<~OUTPUT) - - Cops disabled line ranges: - - lib/rubocop.rb:1..1: LineLength - lib/rubocop.rb:1..Infinity: ClassLength - OUTPUT - end - end - end -end diff --git a/spec/rubocop/options_spec.rb b/spec/rubocop/options_spec.rb index 505367cf633..04f96de1da5 100644 --- a/spec/rubocop/options_spec.rb +++ b/spec/rubocop/options_spec.rb @@ -71,7 +71,6 @@ def abs(path) [p]rogress is used by default [a]utogenconf [c]lang - [d]isabled [e]macs [fi]les [fu]ubar