lib/rspec/core/formatters/deprecation_formatter.rb
RSpec::Support.require_rspec_core "formatters/helpers"
module RSpec
module Core
module Formatters
# @private
class DeprecationFormatter
Formatters.register self, :deprecation, :deprecation_summary
attr_reader :count, :deprecation_stream, :summary_stream
def initialize(deprecation_stream, summary_stream)
@deprecation_stream = deprecation_stream
@summary_stream = summary_stream
@seen_deprecations = Set.new
@count = 0
end
alias :output :deprecation_stream
def printer
@printer ||= case deprecation_stream
when File
ImmediatePrinter.new(FileStream.new(deprecation_stream),
summary_stream, self)
when RaiseErrorStream
ImmediatePrinter.new(deprecation_stream, summary_stream, self)
else
DelayedPrinter.new(deprecation_stream, summary_stream, self)
end
end
def deprecation(notification)
return if @seen_deprecations.include? notification
@count += 1
printer.print_deprecation_message notification
@seen_deprecations << notification
end
def deprecation_summary(_notification)
printer.deprecation_summary
end
def deprecation_message_for(data)
if data.message
SpecifiedDeprecationMessage.new(data)
else
GeneratedDeprecationMessage.new(data)
end
end
RAISE_ERROR_CONFIG_NOTICE = <<-EOS.gsub(/^\s+\|/, '')
|
|If you need more of the backtrace for any of these deprecations to
|identify where to make the necessary changes, you can configure
|`config.raise_errors_for_deprecations!`, and it will turn the
|deprecation warnings into errors, giving you the full backtrace.
EOS
DEPRECATION_STREAM_NOTICE = "Pass `--deprecation-out` or set " \
"`config.deprecation_stream` to a file for full output."
TOO_MANY_WARNINGS_NOTICE = "Too many similar deprecation messages " \
"reported, disregarding further reports. #{DEPRECATION_STREAM_NOTICE}"
# @private
SpecifiedDeprecationMessage = Struct.new(:type) do
def initialize(data)
@message = data.message
super deprecation_type_for(data)
end
def to_s
output_formatted @message
end
def too_many_warnings_message
TOO_MANY_WARNINGS_NOTICE
end
private
def output_formatted(str)
return str unless str.lines.count > 1
separator = '-' * 80
"#{separator}\n#{str.chomp}\n#{separator}"
end
def deprecation_type_for(data)
data.message.gsub(/(\w+\/)+\w+\.rb:\d+/, '')
end
end
# @private
GeneratedDeprecationMessage = Struct.new(:type) do
def initialize(data)
@data = data
super data.deprecated
end
def to_s
msg = String.new("#{@data.deprecated} is deprecated.")
msg << " Use #{@data.replacement} instead." if @data.replacement
msg << " Called from #{@data.call_site}." if @data.call_site
msg
end
def too_many_warnings_message
"Too many uses of deprecated '#{type}'. #{DEPRECATION_STREAM_NOTICE}"
end
end
# @private
class ImmediatePrinter
attr_reader :deprecation_stream, :summary_stream, :deprecation_formatter
def initialize(deprecation_stream, summary_stream, deprecation_formatter)
@deprecation_stream = deprecation_stream
@summary_stream = summary_stream
@deprecation_formatter = deprecation_formatter
end
def print_deprecation_message(data)
deprecation_message = deprecation_formatter.deprecation_message_for(data)
deprecation_stream.puts deprecation_message.to_s
end
def deprecation_summary
return if deprecation_formatter.count.zero?
deprecation_stream.summarize(summary_stream, deprecation_formatter.count)
end
end
# @private
class DelayedPrinter
TOO_MANY_USES_LIMIT = 4
attr_reader :deprecation_stream, :summary_stream, :deprecation_formatter
def initialize(deprecation_stream, summary_stream, deprecation_formatter)
@deprecation_stream = deprecation_stream
@summary_stream = summary_stream
@deprecation_formatter = deprecation_formatter
@seen_deprecations = Hash.new { 0 }
@deprecation_messages = Hash.new { |h, k| h[k] = [] }
end
def print_deprecation_message(data)
deprecation_message = deprecation_formatter.deprecation_message_for(data)
@seen_deprecations[deprecation_message] += 1
stash_deprecation_message(deprecation_message)
end
def stash_deprecation_message(deprecation_message)
if @seen_deprecations[deprecation_message] < TOO_MANY_USES_LIMIT
@deprecation_messages[deprecation_message] << deprecation_message.to_s
elsif @seen_deprecations[deprecation_message] == TOO_MANY_USES_LIMIT
@deprecation_messages[deprecation_message] << deprecation_message.too_many_warnings_message
end
end
def deprecation_summary
return unless @deprecation_messages.any?
print_deferred_deprecation_warnings
deprecation_stream.puts RAISE_ERROR_CONFIG_NOTICE
summary_stream.puts "\n#{Helpers.pluralize(deprecation_formatter.count, 'deprecation warning')} total"
end
def print_deferred_deprecation_warnings
deprecation_stream.puts "\nDeprecation Warnings:\n\n"
@deprecation_messages.keys.sort_by(&:type).each do |deprecation|
messages = @deprecation_messages[deprecation]
messages.each { |msg| deprecation_stream.puts msg }
deprecation_stream.puts
end
end
end
# @private
# Not really a stream, but is usable in place of one.
class RaiseErrorStream
def puts(message)
raise DeprecationError, message
end
def summarize(summary_stream, deprecation_count)
summary_stream.puts "\n#{Helpers.pluralize(deprecation_count, 'deprecation')} found."
end
end
# @private
# Wraps a File object and provides file-specific operations.
class FileStream
def initialize(file)
@file = file
# In one of my test suites, I got lots of duplicate output in the
# deprecation file (e.g. 200 of the same deprecation, even though
# the `puts` below was only called 6 times). Setting `sync = true`
# fixes this (but we really have no idea why!).
@file.sync = true
end
def puts(*args)
@file.puts(*args)
end
def summarize(summary_stream, deprecation_count)
path = @file.respond_to?(:path) ? @file.path : @file.inspect
summary_stream.puts "\n#{Helpers.pluralize(deprecation_count, 'deprecation')} logged to #{path}"
puts RAISE_ERROR_CONFIG_NOTICE
end
end
end
end
# Deprecation Error.
DeprecationError = Class.new(StandardError)
end
end