# frozen_string_literal: true require 'cgi' require 'erb' require 'ostruct' require 'base64' require_relative 'text_util' module RuboCop module Formatter # This formatter saves the output as an html file. class HTMLFormatter < BaseFormatter ELLIPSES = '...'.freeze TEMPLATE_PATH = File.expand_path('../../../assets/output.html.erb', __dir__) Color = Struct.new(:red, :green, :blue, :alpha) do def to_s "rgba(#{values.join(', ')})" end def fade_out(amount) dup.tap do |color| color.alpha -= amount end end end attr_reader :files, :summary def initialize(output, options = {}) super @files = [] @summary = OpenStruct.new(offense_count: 0) end def started(target_files) summary.target_files = target_files end def file_finished(file, offenses) files << OpenStruct.new(path: file, offenses: offenses) summary.offense_count += offenses.count end def finished(inspected_files) summary.inspected_files = inspected_files render_html end def render_html context = ERBContext.new(files, summary) template = File.read(TEMPLATE_PATH, encoding: Encoding::UTF_8) # The following condition is workaround for until Ruby 2.6 is released. # https://github.com/ruby/ruby/commit/cc777d09f44fa909a336ba14f3aa802ffe16e010 erb = if RUBY_VERSION >= '2.6' ERB.new(template, trim_mode: '-') else ERB.new(template, nil, '-') end html = erb.result(context.binding) output.write html end # This class provides helper methods used in the ERB template. class ERBContext include PathUtil include TextUtil SEVERITY_COLORS = { refactor: Color.new(0xED, 0x9C, 0x28, 1.0), convention: Color.new(0xED, 0x9C, 0x28, 1.0), warning: Color.new(0x96, 0x28, 0xEF, 1.0), error: Color.new(0xD2, 0x32, 0x2D, 1.0), fatal: Color.new(0xD2, 0x32, 0x2D, 1.0) }.freeze LOGO_IMAGE_PATH = File.expand_path('../../../assets/logo.png', __dir__) attr_reader :files, :summary def initialize(files, summary) @files = files.sort_by(&:path) @summary = summary end # Make Kernel#binding public. def binding super end def decorated_message(offense) offense.message.gsub(/`(.+?)`/) do "#{Regexp.last_match(1)}" end end def highlighted_source_line(offense) source_before_highlight(offense) + hightlight_source_tag(offense) + source_after_highlight(offense) + possible_ellipses(offense.location) end def hightlight_source_tag(offense) "" \ "#{escape(offense.highlighted_area.source)}" \ '' end def source_before_highlight(offense) source_line = offense.location.source_line escape(source_line[0...offense.highlighted_area.begin_pos]) end def source_after_highlight(offense) source_line = offense.location.source_line escape(source_line[offense.highlighted_area.end_pos..-1]) end def possible_ellipses(location) location.first_line == location.last_line ? '' : " #{ELLIPSES}" end def escape(string) CGI.escapeHTML(string) end def base64_encoded_logo_image image = File.read(LOGO_IMAGE_PATH, binmode: true) Base64.encode64(image) end end end end end