# typed: strict # frozen_string_literal: true require_relative "coverage/snapshot" require_relative "coverage/report" require_relative "file_tree" require "date" module Spoom module Coverage extend T::Sig sig { params(path: String, rbi: T::Boolean, sorbet_bin: T.nilable(String)).returns(Snapshot) } def self.snapshot(path: '.', rbi: true, sorbet_bin: nil) config = sorbet_config(path: path) config.allowed_extensions.push(".rb", ".rbi") if config.allowed_extensions.empty? new_config = config.copy new_config.allowed_extensions.reject! { |ext| !rbi && ext == ".rbi" } metrics = Spoom::Sorbet.srb_metrics( "--no-config", "--no-error-sections", "--no-error-count", "--isolate-error-code=0", new_config.options_string, path: path, capture_err: true, sorbet_bin: sorbet_bin ) snapshot = Snapshot.new return snapshot unless metrics sha = Spoom::Git.last_commit(path: path) snapshot.commit_sha = sha snapshot.commit_timestamp = Spoom::Git.commit_timestamp(sha, path: path).to_i if sha snapshot.files = metrics.fetch("types.input.files", 0) snapshot.modules = metrics.fetch("types.input.modules.total", 0) snapshot.classes = metrics.fetch("types.input.classes.total", 0) snapshot.singleton_classes = metrics.fetch("types.input.singleton_classes.total", 0) snapshot.methods_with_sig = metrics.fetch("types.sig.count", 0) snapshot.methods_without_sig = metrics.fetch("types.input.methods.total", 0) - snapshot.methods_with_sig snapshot.calls_typed = metrics.fetch("types.input.sends.typed", 0) snapshot.calls_untyped = metrics.fetch("types.input.sends.total", 0) - snapshot.calls_typed snapshot.duration += metrics.fetch("run.utilization.system_time.us", 0) snapshot.duration += metrics.fetch("run.utilization.user_time.us", 0) Snapshot::STRICTNESSES.each do |strictness| next unless metrics.key?("types.input.files.sigil.#{strictness}") snapshot.sigils[strictness] = T.must(metrics["types.input.files.sigil.#{strictness}"]) end snapshot.version_static = Spoom::Sorbet.version_from_gemfile_lock(gem: "sorbet-static", path: path) snapshot.version_runtime = Spoom::Sorbet.version_from_gemfile_lock(gem: "sorbet-runtime", path: path) files = Spoom::Sorbet.srb_files(new_config, path: path) snapshot.rbi_files = files.count { |file| file.end_with?(".rbi") } snapshot end sig { params(snapshots: T::Array[Snapshot], palette: D3::ColorPalette, path: String).returns(Report) } def self.report(snapshots, palette:, path: ".") intro_commit = Git.sorbet_intro_commit(path: path) intro_date = intro_commit ? Git.commit_time(intro_commit, path: path) : nil Report.new( project_name: File.basename(File.expand_path(path)), palette: palette, snapshots: snapshots, sigils_tree: sigils_tree(path: path), sorbet_intro_commit: intro_commit, sorbet_intro_date: intro_date, ) end sig { params(path: String).returns(Sorbet::Config) } def self.sorbet_config(path: ".") Sorbet::Config.parse_file("#{path}/#{Spoom::Sorbet::CONFIG_PATH}") end sig { params(path: String).returns(FileTree) } def self.sigils_tree(path: ".") config = sorbet_config(path: path) files = Sorbet.srb_files(config, path: path) extensions = config.allowed_extensions extensions = [".rb"] if extensions.empty? extensions -= [".rbi"] pattern = /\.(#{Regexp.union(extensions.map { |ext| ext[1..-1] })})$/ files.select! { |file| file =~ pattern } files.reject! { |file| file =~ %r{/test/} } FileTree.new(files, strip_prefix: path) end end end