use super::*;
mod architecture;
mod dead_code;
mod dependencies;
pub(crate) use architecture::analyse_architecture_rules;
pub(crate) use dead_code::analyse_project_dead_code_rules;
pub(crate) use dependencies::analyse_dependency_rules;
pub(crate) fn analyse_project(
context: &ProjectContext,
config: &Config,
diagnostics: &mut Vec<RunDiagnostic>,
) -> Vec<Finding> {
let mut findings = Vec::new();
if !project_has_readme(&context.root_path) && config.is_rule_enabled("docs.missing-readme") {
findings.push(Finding::new(FindingDescriptor {
rule_id: "docs.missing-readme".to_string(),
message: "Project root does not contain a README.md file.".to_string(),
file_path: "README.md".to_string(),
line: Some(1),
severity: Severity::Advisory,
pillar: Pillar::Documentation,
confidence: Confidence::High,
symbol: None,
remediation: Some(
"Add a README.md that explains the project purpose and local commands.".to_string(),
),
metadata: json!({}),
}));
}
analyse_dependency_rules(context, config, &mut findings);
analyse_architecture_rules(context, config, &mut findings);
analyse_project_dead_code_rules(context, config, diagnostics, &mut findings);
findings
.into_iter()
.map(|finding| apply_configured_severity(finding, config))
.collect()
}
fn apply_configured_severity(mut finding: Finding, config: &Config) -> Finding {
finding.severity = config.severity(&finding.rule_id, finding.severity);
finding
}
pub(crate) fn project_has_readme(root_path: &Path) -> bool {
fs::read_dir(root_path)
.ok()
.into_iter()
.flat_map(|entries| entries.filter_map(Result::ok))
.any(|entry| {
entry
.file_name()
.to_str()
.is_some_and(|name| name.eq_ignore_ascii_case("README.md"))
})
}
pub(crate) fn module_label(file_path: &str, module_path: &str) -> String {
if module_path.is_empty() {
file_path.to_string()
} else {
module_path.to_string()
}
}
pub(crate) fn item_symbol(item: &ItemSummary) -> String {
if item.kind == "method" {
let qualified_method = match &item.container {
Some(container) => format!("{container}::{}", item.name),
None => item.name.to_string(),
};
return if item.module_path.is_empty() {
qualified_method
} else {
format!("{}::{qualified_method}", item.module_path)
};
}
if item.module_path.is_empty() {
item.name.to_string()
} else {
format!("{}::{}", item.module_path, item.name)
}
}
impl ProjectContext {
pub(crate) fn identifier_count(&self, name: &str) -> usize {
self.identifier_counts
.get(name)
.copied()
.unwrap_or_default()
}
}
pub(crate) fn is_missing_text(value: Option<&str>) -> bool {
value.is_none_or(|value| value.trim().is_empty())
}
pub(crate) fn is_wildcard_requirement(requirement: &str) -> bool {
requirement
.split(',')
.any(|part| part.trim() == "*" || part.trim().ends_with(".*"))
}