crap4rust/invocation/
app.rs1use std::ffi::OsString;
6use std::process::ExitCode;
7
8use anyhow::{Context, Result, bail};
9use clap::Parser;
10
11use crate::analysis::source_function::SourceFunction;
12use crate::analysis::source_function_discovery::SourceFunctionDiscovery;
13use crate::coverage_io::coverage_index::CoverageIndex;
14use crate::coverage_io::llvm_cov_provider::LlvmCovProvider;
15use crate::invocation::cargo_package_resolver::CargoPackageResolver;
16use crate::invocation::cli::Args;
17use crate::invocation::config::Config;
18use crate::invocation::package_context::PackageContext;
19use crate::reporting::default_scorer::DefaultScorer;
20use crate::reporting::project_report::ProjectReport;
21use crate::reporting::stdout_reporter::StdoutReporter;
22use crate::traits::coverage_provider::CoverageProvider;
23use crate::traits::function_discovery::FunctionDiscovery;
24use crate::traits::package_resolver::PackageResolver;
25use crate::traits::reporter::Reporter;
26use crate::traits::scorer::Scorer;
27
28pub struct App {
29 resolver: Box<dyn PackageResolver>,
30 discovery: Box<dyn FunctionDiscovery>,
31 coverage_provider: Box<dyn CoverageProvider>,
32 scorer: Box<dyn Scorer>,
33 reporter: Box<dyn Reporter>,
34 config: Config,
35}
36
37impl App {
38 #[must_use]
39 pub fn new(config: Config) -> Self {
40 Self {
41 resolver: Box::new(CargoPackageResolver::new()),
42 discovery: Box::new(SourceFunctionDiscovery::new()),
43 coverage_provider: Box::new(LlvmCovProvider::new()),
44 scorer: Box::new(DefaultScorer::new()),
45 reporter: Box::new(StdoutReporter::new()),
46 config,
47 }
48 }
49
50 #[must_use]
51 pub fn with_deps(
52 resolver: Box<dyn PackageResolver>,
53 discovery: Box<dyn FunctionDiscovery>,
54 coverage_provider: Box<dyn CoverageProvider>,
55 scorer: Box<dyn Scorer>,
56 reporter: Box<dyn Reporter>,
57 config: Config,
58 ) -> Self {
59 Self {
60 resolver,
61 discovery,
62 coverage_provider,
63 scorer,
64 reporter,
65 config,
66 }
67 }
68
69 pub fn run(&self) -> Result<ExitCode> {
70 let packages = self.resolver.resolve(&self.config)?;
71 let coverage_records = self.coverage_provider.provide(&self.config, &packages)?;
72
73 let functions = self.discover_all_functions(&packages)?;
74 if functions.is_empty() {
75 bail!("no Rust functions were discovered in the selected packages");
76 }
77
78 if coverage_records.is_empty() {
79 bail!("coverage file did not contain any function records");
80 }
81 let coverage_index = CoverageIndex::from_records(coverage_records);
82
83 let matched_count = functions
84 .iter()
85 .filter(|function| coverage_index.match_function(function).is_some())
86 .count();
87 if matched_count == 0 {
88 bail!(
89 "coverage data could not be matched to any discovered function by file path and line"
90 );
91 }
92
93 let reports = self
94 .scorer
95 .score_functions(functions, &coverage_index, &self.config);
96 let metrics = self.scorer.project_metrics(&reports, &self.config);
97
98 let report_data = ProjectReport {
99 scope_name: packages
100 .iter()
101 .map(|package| package.name.clone())
102 .collect::<Vec<_>>()
103 .join(", "),
104 total_functions: metrics.total_functions,
105 crappy_functions: metrics.crappy_functions,
106 crappy_percent: metrics.crappy_percent,
107 verdict: metrics.verdict,
108 functions: reports,
109 };
110
111 self.reporter.render(&report_data, &self.config);
112
113 Ok(report_data.exit_code(&self.config))
114 }
115
116 fn discover_all_functions(&self, packages: &[PackageContext]) -> Result<Vec<SourceFunction>> {
117 let mut functions = Vec::new();
118 for package in packages {
119 let mut package_functions = self.discovery.discover(package).with_context(|| {
120 format!("failed to discover functions in package {}", package.name)
121 })?;
122 functions.append(&mut package_functions);
123 }
124 Ok(functions)
125 }
126}
127
128pub fn run() -> Result<ExitCode> {
133 App::new(Config::from_args(Args::parse_args())).run()
134}
135
136pub fn run_from_args<I, T>(args: I) -> Result<ExitCode>
137where
138 I: IntoIterator<Item = T>,
139 T: Into<OsString> + Clone,
140{
141 let args = <Args as Parser>::parse_from(args);
142 App::new(Config::from_args(args)).run()
143}