use super::super::{CertRule, RuleViolation};
use crate::manifest::{RuleCategory, Severity};
use crate::utility::cert_c::ast_utils::get_node_text;
use tree_sitter::Node;
pub struct Dcl18C;
impl CertRule for Dcl18C {
fn rule_id(&self) -> &'static str {
"DCL18-C"
}
fn description(&self) -> &'static str {
"Do not begin integer constants with 0 when specifying a decimal value"
}
fn severity(&self) -> Severity {
Severity::Low
}
fn category(&self) -> RuleCategory {
RuleCategory::Recommendation
}
fn cert_id(&self) -> &'static str {
"DCL18-C"
}
fn check(&self, node: &Node, source: &str) -> Vec<RuleViolation> {
let mut violations = Vec::new();
violations.extend(self.check_node(*node, source));
violations
}
}
impl Dcl18C {
fn check_node(&self, node: Node, source: &str) -> Vec<RuleViolation> {
let mut violations = Vec::new();
if node.kind() == "number_literal" {
if let Some(violation) = self.check_number_literal(node, source) {
violations.push(violation);
}
}
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
violations.extend(self.check_node(child, source));
}
violations
}
fn check_number_literal(&self, literal_node: Node, source: &str) -> Option<RuleViolation> {
let literal_text = get_node_text(&literal_node, source);
if self.is_unintended_octal(&literal_text) {
let decimal_value = self.parse_octal_as_decimal(&literal_text);
let message = format!(
"Integer constant '{}' begins with 0, making it octal (base-8). This evaluates to {} in decimal. \
If you intended decimal {}, remove the leading 0. \
If you intended octal, consider using explicit base notation for clarity",
literal_text,
self.octal_to_decimal(&literal_text),
decimal_value
);
let suggestion = format!(
"Remove leading 0: use '{}' for decimal, or keep '{}' if octal was intended",
decimal_value, literal_text
);
return Some(RuleViolation {
rule_id: self.rule_id().to_string(),
severity: self.severity(),
message,
file_path: String::new(),
line: literal_node.start_position().row + 1,
column: literal_node.start_position().column + 1,
suggestion: Some(suggestion),
..Default::default()
});
}
None
}
fn is_unintended_octal(&self, literal: &str) -> bool {
if !literal.starts_with('0') {
return false;
}
if literal == "0" {
return false;
}
if literal.starts_with("0x") || literal.starts_with("0X") {
return false;
}
if literal.starts_with("0b") || literal.starts_with("0B") {
return false;
}
if literal.contains('.') || literal.contains('e') || literal.contains('E') {
return false;
}
let stripped = literal.trim_end_matches(['u', 'U', 'l', 'L']);
if stripped == "0" {
return false;
}
if literal.len() > 1 {
return true;
}
false
}
fn parse_octal_as_decimal(&self, literal: &str) -> String {
literal.trim_start_matches('0').to_string()
}
fn octal_to_decimal(&self, literal: &str) -> i64 {
i64::from_str_radix(literal.trim_start_matches("0o").trim_start_matches('0'), 8)
.unwrap_or(0)
}
}