#![cfg_attr(coverage_nightly, coverage(off))]
use super::{
AnalyzeComplexityContract, AnalyzeDeadCodeContract, AnalyzeLintHotspotContract,
AnalyzeSatdContract, AnalyzeTdgContract, BaseAnalysisContract, ContractValidation,
OutputFormat,
};
use crate::cli::commands::AnalyzeCommands;
use anyhow::Result;
use std::path::{Path, PathBuf};
pub struct ContractAdapter;
struct ComplexityMapParams<'a> {
project_path: &'a Path,
_file: &'a Option<PathBuf>,
_files: &'a [PathBuf],
output: &'a Option<PathBuf>,
max_cyclomatic: &'a Option<u16>,
max_cognitive: &'a Option<u16>,
top_files: &'a usize,
timeout: &'a u64,
}
struct SatdMapParams<'a> {
path: &'a Path,
critical_only: &'a bool,
strict: &'a bool,
include_tests: &'a bool,
output: &'a Option<PathBuf>,
top_files: &'a usize,
fail_on_violation: &'a bool,
timeout: &'a u64,
}
struct DeadCodeMapParams<'a> {
path: &'a Path,
top_files: &'a Option<usize>,
include_unreachable: &'a bool,
min_dead_lines: &'a usize,
include_tests: &'a bool,
output: &'a Option<PathBuf>,
fail_on_violation: &'a bool,
max_percentage: &'a f64,
timeout: &'a u64,
}
struct LintHotspotMapParams<'a> {
project_path: &'a Path,
file: &'a Option<PathBuf>,
max_density: &'a f64,
min_confidence: &'a f64,
enforce: &'a bool,
dry_run: &'a bool,
output: &'a Option<PathBuf>,
top_files: &'a usize,
}
impl ContractAdapter {
#[must_use]
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn deprecation_warnings(_cmd: &AnalyzeCommands) -> Vec<String> {
Vec::new()
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn from_cli(cmd: &AnalyzeCommands) -> Result<Box<dyn ContractValidation>> {
match cmd {
AnalyzeCommands::Complexity {
path,
project_path,
file,
files,
output,
max_cyclomatic,
max_cognitive,
top_files,
timeout,
..
} => {
let analysis_path = if let Some(deprecated_path) = project_path {
deprecated_path.clone()
} else {
path.clone()
};
let params = ComplexityMapParams {
project_path: &analysis_path,
_file: file,
_files: files,
output,
max_cyclomatic,
max_cognitive,
top_files,
timeout,
};
Self::map_complexity_command(params)
}
AnalyzeCommands::Satd {
path,
critical_only,
strict,
include_tests,
output,
top_files,
fail_on_violation,
timeout,
..
} => {
let params = SatdMapParams {
path,
critical_only,
strict,
include_tests,
output,
top_files,
fail_on_violation,
timeout,
};
Self::map_satd_command(params)
}
AnalyzeCommands::DeadCode {
path,
top_files,
include_unreachable,
min_dead_lines,
include_tests,
output,
fail_on_violation,
max_percentage,
timeout,
..
} => {
let params = DeadCodeMapParams {
path,
top_files,
include_unreachable,
min_dead_lines,
include_tests,
output,
fail_on_violation,
max_percentage,
timeout,
};
Self::map_dead_code_command(params)
}
AnalyzeCommands::Tdg {
path,
threshold,
top_files,
include_components,
output,
critical_only,
..
} => Self::map_tdg_command(
path,
threshold,
top_files,
include_components,
output,
critical_only,
),
AnalyzeCommands::LintHotspot {
path,
project_path,
file,
max_density,
min_confidence,
enforce,
dry_run,
output,
top_files,
..
} => {
let resolved_path = if let Some(deprecated_path) = project_path {
deprecated_path.clone()
} else {
path.clone()
};
let params = LintHotspotMapParams {
project_path: &resolved_path,
file,
max_density,
min_confidence,
enforce,
dry_run,
output,
top_files,
};
Self::map_lint_hotspot_command(params)
}
_ => {
anyhow::bail!("Command not yet adapted to uniform contract")
}
}
}
fn map_complexity_command(params: ComplexityMapParams) -> Result<Box<dyn ContractValidation>> {
let path = params.project_path;
let contract = AnalyzeComplexityContract {
base: BaseAnalysisContract {
path: path.to_path_buf(),
format: OutputFormat::Table,
output: params.output.clone(),
top_files: Some(*params.top_files),
include_tests: false,
timeout: *params.timeout,
},
max_cyclomatic: params.max_cyclomatic.map(u32::from),
max_cognitive: params.max_cognitive.map(u32::from),
max_halstead: None,
};
contract.validate()?;
Ok(Box::new(contract))
}
fn map_satd_command(params: SatdMapParams) -> Result<Box<dyn ContractValidation>> {
let contract = AnalyzeSatdContract {
base: BaseAnalysisContract {
path: params.path.to_path_buf(),
format: OutputFormat::Summary,
output: params.output.clone(),
top_files: Some(*params.top_files),
include_tests: *params.include_tests,
timeout: *params.timeout,
},
severity: None,
critical_only: *params.critical_only,
strict: *params.strict,
fail_on_violation: *params.fail_on_violation,
};
contract.validate()?;
Ok(Box::new(contract))
}
fn map_dead_code_command(params: DeadCodeMapParams) -> Result<Box<dyn ContractValidation>> {
let contract = AnalyzeDeadCodeContract {
base: BaseAnalysisContract {
path: params.path.to_path_buf(),
format: OutputFormat::Summary,
output: params.output.clone(),
top_files: *params.top_files,
include_tests: *params.include_tests,
timeout: *params.timeout,
},
include_unreachable: *params.include_unreachable,
min_dead_lines: *params.min_dead_lines,
max_percentage: *params.max_percentage,
fail_on_violation: *params.fail_on_violation,
};
contract.validate()?;
Ok(Box::new(contract))
}
fn map_tdg_command(
path: &Path,
threshold: &f64,
top_files: &usize,
include_components: &bool,
output: &Option<PathBuf>,
critical_only: &bool,
) -> Result<Box<dyn ContractValidation>> {
let contract = AnalyzeTdgContract {
base: BaseAnalysisContract {
path: path.to_path_buf(),
format: OutputFormat::Table,
output: output.clone(),
top_files: Some(*top_files),
include_tests: false,
timeout: 60,
},
threshold: *threshold,
include_components: *include_components,
critical_only: *critical_only,
};
contract.validate()?;
Ok(Box::new(contract))
}
fn map_lint_hotspot_command(
params: LintHotspotMapParams,
) -> Result<Box<dyn ContractValidation>> {
let contract = AnalyzeLintHotspotContract {
base: BaseAnalysisContract {
path: params.project_path.to_path_buf(),
format: OutputFormat::Summary,
output: params.output.clone(),
top_files: Some(*params.top_files),
include_tests: false,
timeout: 60,
},
file: params.file.clone(),
max_density: *params.max_density,
min_confidence: *params.min_confidence,
enforce: *params.enforce,
dry_run: *params.dry_run,
};
contract.validate()?;
Ok(Box::new(contract))
}
}
pub struct BackwardCompatibility;
impl BackwardCompatibility {
#[must_use]
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn map_json_params(mut params: serde_json::Value) -> serde_json::Value {
if let Some(obj) = params.as_object_mut() {
Self::map_project_path_to_path(obj);
Self::map_format_types(obj);
}
params
}
#[must_use]
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn map_json_params_for_complexity(mut params: serde_json::Value) -> serde_json::Value {
if let Some(obj) = params.as_object_mut() {
Self::map_project_path_to_path(obj);
Self::map_file_to_files(obj);
Self::map_format_types(obj);
}
params
}
fn map_project_path_to_path(obj: &mut serde_json::Map<String, serde_json::Value>) {
if let Some(project_path) = obj.remove("project_path") {
if !obj.contains_key("path") {
obj.insert("path".to_string(), project_path);
}
}
}
fn map_file_to_files(obj: &mut serde_json::Map<String, serde_json::Value>) {
if let Some(file) = obj.remove("file") {
if !obj.contains_key("files") {
obj.insert("files".to_string(), serde_json::json!([file]));
}
}
}
fn map_format_types(obj: &mut serde_json::Map<String, serde_json::Value>) {
if let Some(format) = obj.get_mut("format") {
if let Some(fmt_str) = format.as_str() {
let unified = Self::normalize_format_string(fmt_str);
*format = serde_json::json!(unified);
}
}
}
fn normalize_format_string(fmt_str: &str) -> &'static str {
match fmt_str {
"human" | "pretty" | "summary" => "summary",
"json" | "machine" => "json",
"yaml" | "yml" => "yaml",
"markdown" | "md" => "markdown",
"csv" | "tsv" => "csv",
_ => "table",
}
}
}
#[cfg(test)]
#[path = "adapter_tests.rs"]
mod tests;