149 lines
4.1 KiB
Ruby
149 lines
4.1 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
require 'set'
|
|
require 'rubocop'
|
|
require 'yaml'
|
|
|
|
require_relative '../todo_dir'
|
|
|
|
module RuboCop
|
|
module Formatter
|
|
# This formatter dumps a YAML configuration file per cop rule
|
|
# into `.rubocop_todo/**/*.yml` which contains detected offenses.
|
|
#
|
|
# 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
|
|
attr_accessor :base_directory
|
|
end
|
|
|
|
self.base_directory = DEFAULT_BASE_DIRECTORY
|
|
|
|
def initialize(output, _options = {})
|
|
@directory = self.class.base_directory
|
|
@todos = Hash.new { |hash, cop_name| hash[cop_name] = Todo.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
|
|
check_multiple_configurations!
|
|
|
|
super
|
|
end
|
|
|
|
def file_finished(file, offenses)
|
|
return if offenses.empty?
|
|
|
|
file = relative_path(file)
|
|
|
|
offenses.map(&:cop_name).tally.each do |cop_name, offense_count|
|
|
@todos[cop_name].record(file, offense_count)
|
|
end
|
|
end
|
|
|
|
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)
|
|
|
|
output.puts "Written to #{relative_path(path)}\n"
|
|
end
|
|
end
|
|
|
|
def self.with_base_directory(directory)
|
|
old = base_directory
|
|
self.base_directory = directory
|
|
|
|
yield
|
|
ensure
|
|
self.base_directory = old
|
|
end
|
|
|
|
private
|
|
|
|
attr_reader :directory
|
|
|
|
def relative_path(path)
|
|
parent = File.expand_path('..', directory)
|
|
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?
|
|
|
|
list = cop_names.sort.map { |cop_name| "- #{cop_name}" }.join("\n")
|
|
raise "Multiple configurations found for cops:\n#{list}\n"
|
|
end
|
|
|
|
def previously_disabled?(todo)
|
|
cop_name = todo.cop_name
|
|
|
|
config = @config_old_todo_yml[cop_name] ||
|
|
@config_inspect_todo_dir[cop_name] || {}
|
|
return false if config.empty?
|
|
|
|
config['Enabled'] == false
|
|
end
|
|
|
|
def load_config_inspect_todo_dir
|
|
@todo_dir.list_inspect.each_with_object({}) do |path, combined|
|
|
config = YAML.load_file(path)
|
|
combined.update(config) if Hash === config
|
|
end
|
|
end
|
|
|
|
# Load YAML configuration from `.rubocop_todo.yml`.
|
|
# We consider this file already old, obsolete, and to be removed soon.
|
|
def load_config_old_todo_yml
|
|
path = File.expand_path(File.join(directory, '../.rubocop_todo.yml'))
|
|
config = YAML.load_file(path) if File.exist?(path)
|
|
|
|
config || {}
|
|
end
|
|
end
|
|
end
|
|
end
|