use tree_sitter::Node;
use super::super::{CertRule, RuleViolation};
use crate::manifest::{RuleCategory, Severity};
use crate::utility::cert_c::ast_utils::get_node_text;
pub struct Exp45C;
impl CertRule for Exp45C {
fn rule_id(&self) -> &'static str {
"EXP45-C"
}
fn description(&self) -> &'static str {
"Do not perform assignments in selection statements"
}
fn severity(&self) -> Severity {
Severity::Medium
}
fn category(&self) -> RuleCategory {
RuleCategory::Rule
}
fn cert_id(&self) -> &'static str {
"EXP45-C"
}
fn check(&self, node: &Node, source: &str) -> Vec<RuleViolation> {
let mut violations = Vec::new();
self.check_node(node, source, &mut violations);
violations
}
}
impl Exp45C {
fn check_node(&self, node: &Node, source: &str, violations: &mut Vec<RuleViolation>) {
match node.kind() {
"if_statement" => {
if let Some(condition) = node.child_by_field_name("condition") {
self.check_condition_for_assignment(&condition, source, "if", violations);
}
}
"switch_statement" => {
if let Some(condition) = node.child_by_field_name("condition") {
self.check_condition_for_assignment(&condition, source, "switch", violations);
}
}
"while_statement" => {
if let Some(condition) = node.child_by_field_name("condition") {
self.check_condition_for_assignment(&condition, source, "while", violations);
}
}
"do_statement" => {
if let Some(condition) = node.child_by_field_name("condition") {
self.check_condition_for_assignment(&condition, source, "do-while", violations);
}
}
"for_statement" => {
if let Some(condition) = node.child_by_field_name("condition") {
self.check_condition_for_assignment(&condition, source, "for", violations);
}
}
_ => {}
}
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
self.check_node(&child, source, violations);
}
}
fn check_condition_for_assignment(
&self,
condition: &Node,
source: &str,
statement_type: &str,
violations: &mut Vec<RuleViolation>,
) {
self.find_assignments_in_condition(condition, source, statement_type, violations);
}
fn find_assignments_in_condition(
&self,
node: &Node,
source: &str,
statement_type: &str,
violations: &mut Vec<RuleViolation>,
) {
if node.kind() == "assignment_expression" {
if !self.is_assignment_in_comparison(node) && !self.is_in_first_comma_operand(node) {
let line = node.start_position().row + 1;
let column = node.start_position().column + 1;
let assignment_text = get_node_text(node, source);
violations.push(RuleViolation {
rule_id: self.rule_id().to_string(),
severity: self.severity(),
message: format!(
"Assignment in {} statement condition: '{}'",
statement_type,
assignment_text
.split('\n')
.next()
.unwrap_or(&assignment_text)
),
file_path: String::new(),
line,
column,
suggestion: Some(format!(
"Use == for comparison, or move assignment outside {} condition",
statement_type
)),
requires_manual_review: None,
});
}
}
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
self.find_assignments_in_condition(&child, source, statement_type, violations);
}
}
fn is_assignment_in_comparison(&self, assignment_node: &Node) -> bool {
if let Some(parent) = assignment_node.parent() {
match parent.kind() {
"binary_expression" => {
let mut cursor = parent.walk();
for child in parent.children(&mut cursor) {
if matches!(child.kind(), "!=" | "==" | "<" | ">" | "<=" | ">=") {
return true;
}
}
}
"parenthesized_expression" => {
return self.is_assignment_in_comparison(&parent);
}
_ => {}
}
}
false
}
fn is_in_first_comma_operand(&self, assignment_node: &Node) -> bool {
if let Some(parent) = assignment_node.parent() {
if parent.kind() == "comma_expression" {
if let Some(left) = parent.child_by_field_name("left") {
if left.id() == assignment_node.id() {
return true;
}
return self.is_ancestor_of(&left, assignment_node);
}
}
return self.is_in_first_comma_operand(&parent);
}
false
}
fn is_ancestor_of(&self, ancestor: &Node, target: &Node) -> bool {
let mut cursor = ancestor.walk();
for child in ancestor.children(&mut cursor) {
if child.id() == target.id() {
return true;
}
if self.is_ancestor_of(&child, target) {
return true;
}
}
false
}
}