diff --git a/rubocop/cop_todo.rb b/rubocop/cop_todo.rb new file mode 100644 index 0000000000000000000000000000000000000000..42e2f9fbe13ea35b62ba1bcda80cbd67f962d02d --- /dev/null +++ b/rubocop/cop_todo.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +module RuboCop + class CopTodo + attr_accessor :previously_disabled + + attr_reader :cop_name, :files, :offense_count + + def initialize(cop_name) + @cop_name = cop_name + @files = Set.new + @offense_count = 0 + @cop_class = self.class.find_cop_by_name(cop_name) + @previously_disabled = false + end + + def record(file, offense_count) + @files << file + @offense_count += offense_count + end + + def autocorrectable? + @cop_class&.support_autocorrect? + end + + def to_yaml + yaml = [] + yaml << '---' + yaml << '# Cop supports --auto-correct.' if autocorrectable? + yaml << "#{cop_name}:" + + if previously_disabled + yaml << " # Offense count: #{offense_count}" + yaml << ' # Temporarily disabled due to too many offenses' + yaml << ' Enabled: false' + end + + yaml << ' Exclude:' + yaml.concat files.sort.map { |file| " - '#{file}'" } + yaml << '' + + yaml.join("\n") + end + + def self.find_cop_by_name(cop_name) + RuboCop::Cop::Registry.global.find_by_cop_name(cop_name) + end + end +end diff --git a/rubocop/formatter/todo_formatter.rb b/rubocop/formatter/todo_formatter.rb index 662cc1551ffb880fd6d513f5712b724bf0527ece..789d0418f96c1ebca4da2f44dc45029ff9f977e2 100644 --- a/rubocop/formatter/todo_formatter.rb +++ b/rubocop/formatter/todo_formatter.rb @@ -5,6 +5,7 @@ require 'yaml' require_relative '../todo_dir' +require_relative '../cop_todo' module RuboCop module Formatter @@ -14,26 +15,6 @@ module Formatter # For example, this formatter stores offenses for `RSpec/VariableName` # in `.rubocop_todo/rspec/variable_name.yml`. class TodoFormatter < BaseFormatter - class Todo - attr_reader :cop_name, :files, :offense_count - - def initialize(cop_name) - @cop_name = cop_name - @files = Set.new - @offense_count = 0 - @cop_class = RuboCop::Cop::Registry.global.find_by_cop_name(cop_name) - end - - def record(file, offense_count) - @files << file - @offense_count += offense_count - end - - def autocorrectable? - @cop_class&.support_autocorrect? - end - end - DEFAULT_BASE_DIRECTORY = File.expand_path('../../.rubocop_todo', __dir__) class << self @@ -44,7 +25,7 @@ class << self def initialize(output, _options = {}) @directory = self.class.base_directory - @todos = Hash.new { |hash, cop_name| hash[cop_name] = Todo.new(cop_name) } + @todos = Hash.new { |hash, cop_name| hash[cop_name] = CopTodo.new(cop_name) } @todo_dir = TodoDir.new(directory) @config_inspect_todo_dir = load_config_inspect_todo_dir @config_old_todo_yml = load_config_old_todo_yml @@ -65,8 +46,8 @@ def file_finished(file, offenses) def finished(_inspected_files) @todos.values.sort_by(&:cop_name).each do |todo| - yaml = to_yaml(todo) - path = @todo_dir.write(todo.cop_name, yaml) + todo.previously_disabled = previously_disabled?(todo) + path = @todo_dir.write(todo.cop_name, todo.to_yaml) output.puts "Written to #{relative_path(path)}\n" end @@ -90,27 +71,6 @@ def relative_path(path) path.delete_prefix("#{parent}/") end - def to_yaml(todo) - yaml = [] - yaml << '---' - yaml << '# Cop supports --auto-correct.' if todo.autocorrectable? - yaml << "#{todo.cop_name}:" - - if previously_disabled?(todo) - yaml << " # Offense count: #{todo.offense_count}" - yaml << ' # Temporarily disabled due to too many offenses' - yaml << ' Enabled: false' - end - - yaml << ' Exclude:' - - files = todo.files.sort.map { |file| " - '#{file}'" } - yaml.concat files - yaml << '' - - yaml.join("\n") - end - def check_multiple_configurations! cop_names = @config_inspect_todo_dir.keys & @config_old_todo_yml.keys return if cop_names.empty? diff --git a/spec/rubocop/cop_todo_spec.rb b/spec/rubocop/cop_todo_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..978df2c01ee95c25289d42cd61381d978d58ebae --- /dev/null +++ b/spec/rubocop/cop_todo_spec.rb @@ -0,0 +1,124 @@ +# frozen_string_literal: true + +require 'fast_spec_helper' +require_relative '../../rubocop/cop_todo' + +RSpec.describe RuboCop::CopTodo do + let(:cop_name) { 'Cop/Rule' } + + subject(:cop_todo) { described_class.new(cop_name) } + + describe '#initialize' do + it 'initializes a cop todo' do + expect(cop_todo).to have_attributes( + cop_name: cop_name, + files: be_empty, + offense_count: 0, + previously_disabled: false + ) + end + end + + describe '#record' do + it 'records offenses' do + cop_todo.record('a.rb', 1) + cop_todo.record('b.rb', 2) + + expect(cop_todo).to have_attributes( + files: contain_exactly('a.rb', 'b.rb'), + offense_count: 3 + ) + end + end + + describe '#autocorrectable?' do + subject { cop_todo.autocorrectable? } + + context 'when found in rubocop registry' do + before do + fake_cop = double(:cop, support_autocorrect?: autocorrectable) # rubocop:disable RSpec/VerifiedDoubles + + allow(described_class).to receive(:find_cop_by_name) + .with(cop_name).and_return(fake_cop) + end + + context 'when autocorrectable' do + let(:autocorrectable) { true } + + it { is_expected.to be_truthy } + end + + context 'when not autocorrectable' do + let(:autocorrectable) { false } + + it { is_expected.to be_falsey } + end + end + + context 'when not found in rubocop registry' do + before do + allow(described_class).to receive(:find_cop_by_name) + .with(cop_name).and_return(nil).and_call_original + end + + it { is_expected.to be_falsey } + end + end + + describe '#to_yaml' do + subject(:yaml) { cop_todo.to_yaml } + + context 'when autocorrectable' do + before do + allow(cop_todo).to receive(:autocorrectable?).and_return(true) + end + + specify do + expect(yaml).to eq(<<~YAML) + --- + # Cop supports --auto-correct. + #{cop_name}: + Exclude: + YAML + end + end + + context 'when previously disabled' do + specify do + cop_todo.record('a.rb', 1) + cop_todo.record('b.rb', 2) + cop_todo.previously_disabled = true + + expect(yaml).to eq(<<~YAML) + --- + #{cop_name}: + # Offense count: 3 + # Temporarily disabled due to too many offenses + Enabled: false + Exclude: + - 'a.rb' + - 'b.rb' + YAML + end + end + + context 'with multiple files' do + before do + cop_todo.record('a.rb', 0) + cop_todo.record('c.rb', 0) + cop_todo.record('b.rb', 0) + end + + it 'sorts excludes alphabetically' do + expect(yaml).to eq(<<~YAML) + --- + #{cop_name}: + Exclude: + - 'a.rb' + - 'b.rb' + - 'c.rb' + YAML + end + end + end +end diff --git a/spec/rubocop/formatter/todo_formatter_spec.rb b/spec/rubocop/formatter/todo_formatter_spec.rb index fcff028f07d222cdc14d1f6e30350fb38706ddcd..df56ee4593155230759a517135fdbc95526ec804 100644 --- a/spec/rubocop/formatter/todo_formatter_spec.rb +++ b/spec/rubocop/formatter/todo_formatter_spec.rb @@ -261,16 +261,12 @@ def fake_offense(cop_name) double(:offense, cop_name: cop_name) end - def stub_rubocop_registry(**cops) - rubocop_registry = double(:rubocop_registry) - - allow(RuboCop::Cop::Registry).to receive(:global).and_return(rubocop_registry) - - allow(rubocop_registry).to receive(:find_by_cop_name) - .with(String).and_return(nil) + def stub_rubocop_registry(cops) + allow(RuboCop::CopTodo).to receive(:find_cop_by_name) + .with(String).and_return(nil).and_call_original cops.each do |cop_name, attributes| - allow(rubocop_registry).to receive(:find_by_cop_name) + allow(RuboCop::CopTodo).to receive(:find_cop_by_name) .with(cop_name).and_return(fake_cop(**attributes)) end end