use super::super::{CertRule, RuleViolation};
use crate::manifest::{RuleCategory, Severity};
use lang_parsing_substrate::query;
use tree_sitter::Node;
#[derive(Debug)]
pub struct Msc01C;
impl Msc01C {
#[allow(dead_code)]
pub fn new() -> Self {
Msc01C
}
fn check_missing_final_else(
&self,
if_stmt: &Node,
is_continuation: bool,
violations: &mut Vec<RuleViolation>,
) {
match if_stmt.child_by_field_name("alternative") {
None => {
if !is_continuation {
return;
}
let pos = if_stmt.start_position();
violations.push(RuleViolation {
rule_id: "MSC01-C".to_string(),
severity: Severity::Medium,
line: pos.row + 1,
column: pos.column + 1,
message: "if / else-if chain has no final else clause -- the case where none of the conditions hold is unhandled".to_string(),
file_path: String::new(),
suggestion: Some("Add a final else clause to handle the case where none of the preceding conditions hold".to_string()),
requires_manual_review: Some(false),
});
}
Some(alternative) => {
if let Some(else_body) = alternative.named_child(0) {
if else_body.kind() == "if_statement" {
self.check_missing_final_else(&else_body, true, violations);
}
}
}
}
}
fn check_switch(&self, switch_stmt: &Node, source: &str, violations: &mut Vec<RuleViolation>) {
let Some(body) = switch_stmt.child_by_field_name("body") else {
return;
};
let has_default = query::find_first_descendant(body, |n| n.kind() == "default").is_some();
if has_default {
return;
}
let Some(parent) = switch_stmt.parent() else {
return;
};
if parent.kind() != "compound_statement" {
return;
}
let mut cursor = parent.walk();
let siblings: Vec<Node> = parent
.named_children(&mut cursor)
.filter(|n| n.kind() != "comment")
.collect();
if siblings.last() != Some(switch_stmt) {
return;
}
let _ = source;
let pos = switch_stmt.start_position();
violations.push(RuleViolation {
rule_id: "MSC01-C".to_string(),
severity: Severity::Medium,
line: pos.row + 1,
column: pos.column + 1,
message: "switch statement has no default label and no fallback statement follows it -- an unanticipated value is silently unhandled".to_string(),
file_path: String::new(),
suggestion: Some("Add a default label, or a fallback statement after the switch, to handle values not covered by the existing case labels".to_string()),
requires_manual_review: Some(false),
});
}
fn traverse(&self, root: &Node, source: &str, violations: &mut Vec<RuleViolation>) {
for if_stmt in query::find_descendants_of_kind(*root, "if_statement") {
let is_else_if = if_stmt
.parent()
.map(|p| p.kind() == "else_clause")
.unwrap_or(false);
if is_else_if {
continue;
}
self.check_missing_final_else(&if_stmt, false, violations);
}
for switch_stmt in query::find_descendants_of_kind(*root, "switch_statement") {
self.check_switch(&switch_stmt, source, violations);
}
}
}
impl CertRule for Msc01C {
fn rule_id(&self) -> &'static str {
"MSC01-C"
}
fn description(&self) -> &'static str {
"Strive for logical completeness"
}
fn category(&self) -> RuleCategory {
RuleCategory::Rule
}
fn severity(&self) -> Severity {
Severity::Medium
}
fn cert_id(&self) -> &'static str {
"MSC01-C"
}
fn scan(&self, root: &Node, source: &str, violations: &mut Vec<RuleViolation>) {
self.traverse(root, source, violations);
}
}