# typed: strict # frozen_string_literal: true require_relative "d3" require "erb" module Spoom module Coverage class Template extend T::Sig extend T::Helpers abstract! # Create a new template from an Erb file path sig { params(template: String).void } def initialize(template:) @template = template end sig { returns(String) } def erb File.read(@template) end sig { returns(String) } def html ERB.new(erb).result(get_binding) end sig { returns(Binding) } def get_binding # rubocop:disable Naming/AccessorMethodName binding end end class Page < Template extend T::Sig extend T::Helpers abstract! TEMPLATE = T.let("#{Spoom::SPOOM_PATH}/templates/page.erb", String) sig { returns(String) } attr_reader :title sig { returns(D3::ColorPalette) } attr_reader :palette sig { params(title: String, palette: D3::ColorPalette, template: String).void } def initialize(title:, palette:, template: TEMPLATE) super(template: template) @title = title @palette = palette end sig { returns(String) } def header_style D3.header_style end sig { returns(String) } def header_script D3.header_script(palette) end sig { returns(String) } def header_html "

#{title}

" end sig { returns(String) } def body_html cards.map(&:html).join("\n") end sig { abstract.returns(T::Array[Cards::Card]) } def cards; end sig { returns(String) } def footer_html "Generated by spoom on #{Time.now.utc}." end end module Cards class Card < Template extend T::Sig TEMPLATE = T.let("#{Spoom::SPOOM_PATH}/templates/card.erb", String) sig { returns(T.nilable(String)) } attr_reader :title, :body sig { params(template: String, title: T.nilable(String), body: T.nilable(String)).void } def initialize(template: TEMPLATE, title: nil, body: nil) super(template: template) @title = title @body = body end end class Erb < Card extend T::Sig extend T::Helpers abstract! sig { void } def initialize; end # rubocop:disable Lint/MissingSuper sig { override.returns(String) } def html ERB.new(erb).result(get_binding) end sig { abstract.returns(String) } def erb; end end class Snapshot < Card extend T::Sig TEMPLATE = T.let("#{Spoom::SPOOM_PATH}/templates/card_snapshot.erb", String) sig { returns(Coverage::Snapshot) } attr_reader :snapshot sig { params(snapshot: Coverage::Snapshot, title: String).void } def initialize(snapshot:, title: "Snapshot") super(template: TEMPLATE, title: title) @snapshot = snapshot end sig { returns(D3::Pie::Sigils) } def pie_sigils D3::Pie::Sigils.new("pie_sigils", "Sigils", snapshot) end sig { returns(D3::Pie::Calls) } def pie_calls D3::Pie::Calls.new("pie_calls", "Calls", snapshot) end sig { returns(D3::Pie::Sigs) } def pie_sigs D3::Pie::Sigs.new("pie_sigs", "Sigs", snapshot) end end class Map < Card extend T::Sig sig do params( file_tree: FileTree, nodes_strictnesses: T::Hash[FileTree::Node, T.nilable(String)], nodes_strictness_scores: T::Hash[FileTree::Node, Float], title: String, ).void end def initialize(file_tree:, nodes_strictnesses:, nodes_strictness_scores:, title: "Strictness Map") super( title: title, body: D3::CircleMap::Sigils.new( "map_sigils", file_tree, nodes_strictnesses, nodes_strictness_scores, ).html ) end end class Timeline < Card extend T::Sig sig { params(title: String, timeline: D3::Timeline).void } def initialize(title:, timeline:) super(title: title, body: timeline.html) end class Sigils < Timeline extend T::Sig sig { params(snapshots: T::Array[Coverage::Snapshot], title: String).void } def initialize(snapshots:, title: "Sigils Timeline") super(title: title, timeline: D3::Timeline::Sigils.new("timeline_sigils", snapshots)) end end class Calls < Timeline extend T::Sig sig { params(snapshots: T::Array[Coverage::Snapshot], title: String).void } def initialize(snapshots:, title: "Calls Timeline") super(title: title, timeline: D3::Timeline::Calls.new("timeline_calls", snapshots)) end end class Sigs < Timeline extend T::Sig sig { params(snapshots: T::Array[Coverage::Snapshot], title: String).void } def initialize(snapshots:, title: "Signatures Timeline") super(title: title, timeline: D3::Timeline::Sigs.new("timeline_sigs", snapshots)) end end class RBIs < Timeline extend T::Sig sig { params(snapshots: T::Array[Coverage::Snapshot], title: String).void } def initialize(snapshots:, title: "RBIs Timeline") super(title: title, timeline: D3::Timeline::RBIs.new("timeline_rbis", snapshots)) end end class Versions < Timeline extend T::Sig sig { params(snapshots: T::Array[Coverage::Snapshot], title: String).void } def initialize(snapshots:, title: "Sorbet Versions Timeline") super(title: title, timeline: D3::Timeline::Versions.new("timeline_versions", snapshots)) end end class Runtimes < Timeline extend T::Sig sig { params(snapshots: T::Array[Coverage::Snapshot], title: String).void } def initialize(snapshots:, title: "Sorbet Typechecking Time") super(title: title, timeline: D3::Timeline::Runtimes.new("timeline_runtimes", snapshots)) end end end class SorbetIntro < Erb extend T::Sig sig { params(sorbet_intro_commit: T.nilable(String), sorbet_intro_date: T.nilable(Time)).void } def initialize(sorbet_intro_commit: nil, sorbet_intro_date: nil) # rubocop:disable Lint/MissingSuper @sorbet_intro_commit = sorbet_intro_commit @sorbet_intro_date = sorbet_intro_date end sig { override.returns(String) } def erb <<~ERB
Typchecked by Sorbet since #{@sorbet_intro_date&.strftime("%F")} (commit #{@sorbet_intro_commit}).
ERB end end end class Report < Page extend T::Sig sig do params( project_name: String, palette: D3::ColorPalette, snapshots: T::Array[Snapshot], file_tree: FileTree, nodes_strictnesses: T::Hash[FileTree::Node, T.nilable(String)], nodes_strictness_scores: T::Hash[FileTree::Node, Float], sorbet_intro_commit: T.nilable(String), sorbet_intro_date: T.nilable(Time), ).void end def initialize( project_name:, palette:, snapshots:, file_tree:, nodes_strictnesses:, nodes_strictness_scores:, sorbet_intro_commit: nil, sorbet_intro_date: nil ) super(title: project_name, palette: palette) @project_name = project_name @snapshots = snapshots @file_tree = file_tree @nodes_strictnesses = nodes_strictnesses @nodes_strictness_scores = nodes_strictness_scores @sorbet_intro_commit = sorbet_intro_commit @sorbet_intro_date = sorbet_intro_date end sig { override.returns(String) } def header_html last = T.must(@snapshots.last) <<~ERB

#{@project_name} #{last.commit_sha}

ERB end sig { override.returns(T::Array[Cards::Card]) } def cards last = T.must(@snapshots.last) cards = [] cards << Cards::Snapshot.new(snapshot: last) cards << Cards::Map.new( file_tree: @file_tree, nodes_strictnesses: @nodes_strictnesses, nodes_strictness_scores: @nodes_strictness_scores, ) cards << Cards::Timeline::Sigils.new(snapshots: @snapshots) cards << Cards::Timeline::Calls.new(snapshots: @snapshots) cards << Cards::Timeline::Sigs.new(snapshots: @snapshots) cards << Cards::Timeline::RBIs.new(snapshots: @snapshots) cards << Cards::Timeline::Versions.new(snapshots: @snapshots) cards << Cards::Timeline::Runtimes.new(snapshots: @snapshots) cards << Cards::SorbetIntro.new( sorbet_intro_commit: @sorbet_intro_commit, sorbet_intro_date: @sorbet_intro_date, ) cards end end end end