lib/foodcritic/junit/outputter.rb
require 'foodcritic/junit/version' require 'fileutils' require 'pathname' require 'time' module Foodcritic module Junit class Outputter attr_reader :violations, :cookbook_name, :current_violation, :current_violation_lines, :output_dir, :output_file, :current_file_name, :input def initialize(input) @input = input @violations = [] @cookbook_name = ENV['COOKBOOK_NAME'] || File.basename(Dir.pwd) @output_dir = Pathname.new(ENV['FOODCRITIC_JUNIT_OUTPUT_DIR'] || 'test/reports') @output_file = ENV['FOODCRITIC_JUNIT_OUTPUT_FILE'] || 'foodcritic-report.xml' end def run parse_violations write_output end def store_violation if current_violation violations.push({ rule: current_violation[:rule], message: current_violation[:message], file_name: current_file_name, lines: current_violation_lines.join("\n") }) end end def parse_violations input.each_line do |line| line.chomp! if File.exist?(line) || %w(README.md LICENSE).include?(line) store_violation @current_violation = nil @current_violation_lines = [] @current_file_name = line end violation = line.match(/(?<rule>[A-Z]+\d+?):\s(?<message>.+?)$/) if violation # We got a new violation, store the current one + it's lines store_violation @current_violation = violation @current_violation_lines = [] else current_violation_lines << line.lstrip if current_violation_lines end end store_violation end def output_file_path output_dir.join(output_file) end def write_output output_dir.mkpath File.open(output_file_path, 'w') { |f| f.puts(xml) } STDERR.puts("Wrote #{output_file_path}") end def xml <<-EOS <?xml version="1.0" encoding="UTF-8"?> <testsuites> <testsuite name=#{cookbook_name.encode(xml: :attr)} timestamp=#{Time.now.utc.iso8601.to_s.encode(xml: :attr)}> #{violations_as_xml} </testsuite> </testsuites> EOS end def violations_as_xml if violations.any? violations.map { |v| xml_for_violation(v) }.join("\n") else '<testcase classname="foodcritic-junit" name="No Errors"/>' end end def xml_for_violation(violation) name = "#{violation[:rule]}: #{violation[:message]}".encode(xml: :attr) file_name = violation[:file_name].encode(xml: :attr) location = "Located in #{violation[:file_name]}".encode(xml: :attr) <<-EOS <testcase name=#{name} classname=#{file_name} assertions="0" time="0"> <error type=#{name} message=#{location}> #{violation[:lines].encode(xml: :text)} </error> </testcase> EOS end end end end