Skip to content

Instantly share code, notes, and snippets.

@mfittko
Created March 30, 2025 00:48
Show Gist options
  • Save mfittko/a83a5e30fc5a39c724a34e30372df4f4 to your computer and use it in GitHub Desktop.
Save mfittko/a83a5e30fc5a39c724a34e30372df4f4 to your computer and use it in GitHub Desktop.
The missing SimpleCov terminal formatter. Shows only missing lines.
# frozen_string_literal: true
require 'colorize'
require 'tty-table'
module SimpleCov
module Formatter
class MissingFormatter
def initialize(output = nil)
@output = output || $stdout
end
def format(result)
has_imperfect_files = print_groups(result)
print_summary(result, has_imperfect_files)
@output.puts "\n"
end
def color(percent)
case percent
when 90..100 then :green
when 80..90 then :yellow
else :red
end
end
private
def print_groups(result)
has_imperfect_files = false
result.groups.each do |name, files|
imperfect_files = files.select { |file| file.covered_percent < 100 }
next if imperfect_files.empty?
unless has_imperfect_files
@output.puts "\nSimpleCov Missing Coverage Report:".colorize(mode: :bold)
@output.puts '=============================================='
has_imperfect_files = true
end
print_group_header(name, files.covered_percent)
print_imperfect_files(imperfect_files)
end
has_imperfect_files
end
def print_group_header(name, covered_percent)
percentage = covered_percent.round(1)
emoji = if percentage >= 90
'✅'
else
percentage >= 80 ? '⚠️' : '❌'
end
header = " #{emoji} #{name}: #{percentage.to_s.rjust(5)}%"
@output.puts "\n#{header}".colorize(color: color(percentage), mode: :bold)
end
def print_imperfect_files(files)
table = TTY::Table.new(header: ['Coverage', 'File', 'Missing Lines'])
files.sort_by(&:covered_percent).each do |file|
percentage = file.covered_percent.round(2)
filename = file.filename.gsub(%r{^#{Dir.pwd}/}, '')
missed_lines = file.missed_lines.map(&:line_number)
lines_str = missed_lines.empty? ? '' : format_line_ranges(missed_lines)
table << [
" • #{percentage.to_s.rjust(5)}%".colorize(color(percentage)),
filename,
wrap_long_lines(lines_str)
]
end
@output.puts table.render(:unicode, padding: [0, 1, 0, 1], multiline: true)
end
def print_summary(result, has_imperfect_files)
if has_imperfect_files
percentage = result.covered_percent.round(1)
emoji = if percentage >= 90
'✅'
else
percentage >= 80 ? '⚠️' : '❌'
end
summary = " #{emoji} Total: #{percentage.to_s.rjust(5)}%"
@output.puts "\n#{summary}".colorize(color: color(percentage), mode: :bold)
else
@output.puts "\n🎉 Perfect Coverage! All files at 100%! 🎉".colorize(color: :green, mode: :bold)
end
end
def format_line_ranges(numbers)
return '' if numbers.empty?
ranges = []
start_num = numbers.first
prev_num = start_num
numbers.drop(1).each do |num|
if num == prev_num + 1
prev_num = num
else
ranges << (start_num == prev_num ? start_num.to_s : "#{start_num}-#{prev_num}")
start_num = prev_num = num
end
end
ranges << (start_num == prev_num ? start_num.to_s : "#{start_num}-#{prev_num}")
ranges.join(', ')
end
def wrap_long_lines(lines_str)
return '' if lines_str.empty?
if lines_str.length > 60
lines_str.scan(/.{1,60}(?:,|$)/).join("\n").colorize(:red)
else
lines_str.colorize(:red)
end
end
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment