use std::collections::HashSet;
use l5x::{Controller, UDIDefinitionContent};
use crate::config::UnusedDataTypesConfig;
use crate::report::{Report, Severity, Rule, RuleKind};
pub struct UnusedDataTypesDetector<'a> {
config: &'a UnusedDataTypesConfig,
}
impl<'a> UnusedDataTypesDetector<'a> {
pub fn new(config: &'a UnusedDataTypesConfig) -> Self {
Self { config }
}
pub fn detect(&self, controller: &Controller, report: &mut Report) {
if !self.config.enabled {
return;
}
let defined_types = self.collect_defined_datatypes(controller);
let used_types = self.collect_used_datatypes(controller);
for type_name in &defined_types {
if used_types.contains(type_name.as_str()) {
continue;
}
if self.matches_ignore_pattern(type_name) {
continue;
}
report.add(Rule::new(
RuleKind::UnusedDataType,
Severity::Info,
"DataTypes".to_string(),
type_name.clone(),
format!("DataType '{}' is defined but never used", type_name),
));
}
}
fn collect_defined_datatypes(&self, controller: &Controller) -> Vec<String> {
let mut types = Vec::new();
if let Some(ref datatypes) = controller.data_types {
for dt in &datatypes.data_type {
types.push(dt.name.clone());
}
}
types
}
fn collect_used_datatypes(&self, controller: &Controller) -> HashSet<String> {
let mut used = HashSet::new();
if let Some(ref tags) = controller.tags {
for tag in &tags.tag {
if let Some(ref dt) = tag.data_type {
used.insert(dt.clone());
}
}
}
if let Some(ref programs) = controller.programs {
for program in &programs.program {
if let Some(ref tags) = program.tags {
for tag in &tags.tag {
if let Some(ref dt) = tag.data_type {
used.insert(dt.clone());
}
}
}
}
}
if let Some(ref aois) = controller.add_on_instruction_definitions {
for aoi in &aois.add_on_instruction_definition {
for content in &aoi.content {
match content {
UDIDefinitionContent::Parameters(params) => {
for param in ¶ms.parameter {
if let Some(ref dt) = param.data_type {
used.insert(dt.clone());
}
}
}
UDIDefinitionContent::LocalTags(local_tags) => {
for tag in &local_tags.local_tag {
used.insert(tag.data_type.clone());
}
}
_ => {}
}
}
}
}
if let Some(ref datatypes) = controller.data_types {
for dt in &datatypes.data_type {
if let Some(ref members) = dt.members {
for member in &members.member {
used.insert(member.data_type.clone());
}
}
}
}
used
}
fn matches_ignore_pattern(&self, type_name: &str) -> bool {
for pattern in &self.config.ignore_patterns {
if glob_match(pattern, type_name) {
return true;
}
}
false
}
}
fn glob_match(pattern: &str, text: &str) -> bool {
let pattern_chars: Vec<char> = pattern.chars().collect();
let text_chars: Vec<char> = text.chars().collect();
glob_match_recursive(&pattern_chars, &text_chars, 0, 0)
}
fn glob_match_recursive(pattern: &[char], text: &[char], pi: usize, ti: usize) -> bool {
if pi == pattern.len() {
return ti == text.len();
}
match pattern[pi] {
'*' => {
for i in ti..=text.len() {
if glob_match_recursive(pattern, text, pi + 1, i) {
return true;
}
}
false
}
'?' => {
if ti < text.len() {
glob_match_recursive(pattern, text, pi + 1, ti + 1)
} else {
false
}
}
c => {
if ti < text.len() && c.eq_ignore_ascii_case(&text[ti]) {
glob_match_recursive(pattern, text, pi + 1, ti + 1)
} else {
false
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_glob_match() {
assert!(glob_match("UDT_*", "UDT_Motor"));
assert!(glob_match("*_Type", "MyCustom_Type"));
assert!(!glob_match("UDT_*", "Motor_UDT"));
}
}