# 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