use std::path::Path;
use l5x::Controller;
use crate::analysis::{analyze_controller, analyze_plcopen_project, ParseStats, PlcopenStats};
use crate::config::RuleConfig;
use crate::loader::LoadedProject;
use crate::report::{Report, Severity};
use crate::rules::{
ComplexityDetector, EmptyRoutinesDetector, NestingDetector,
UndefinedTagsDetector, UnusedAoisDetector, UnusedDataTypesDetector, UnusedTagsDetector,
PlcopenUnusedVarsDetector, PlcopenUndefinedVarsDetector, PlcopenEmptyPousDetector,
};
use crate::Result;
pub struct RuleDetector {
config: RuleConfig,
}
impl RuleDetector {
pub fn new() -> Self {
Self {
config: RuleConfig::default(),
}
}
pub fn with_config(config: RuleConfig) -> Self {
Self { config }
}
pub fn from_config_file(path: &Path) -> Result<Self> {
let config = RuleConfig::from_file(path)?;
Ok(Self { config })
}
pub fn config(&self) -> &RuleConfig {
&self.config
}
pub fn min_severity(&self) -> Severity {
Severity::parse(&self.config.general.min_severity).unwrap_or(Severity::Info)
}
pub fn analyze_file(&self, path: &Path) -> Result<Report> {
let project = LoadedProject::from_file(path)?;
let mut report = self.analyze(&project)?;
report.source_file = project.source_path;
Ok(report)
}
pub fn analyze(&self, project: &LoadedProject) -> Result<Report> {
if let Some(ref controller) = project.l5x_controller {
return self.analyze_controller(controller);
}
if let Some(ref plcopen) = project.plcopen_project {
return self.analyze_plcopen(plcopen, project.source_path.clone());
}
Ok(Report::new())
}
fn analyze_plcopen(&self, project: &plcopen::Project, source_path: Option<String>) -> Result<Report> {
let analysis = analyze_plcopen_project(project);
let mut report = Report::new();
report.source_file = source_path;
let unused_detector = PlcopenUnusedVarsDetector::new(&self.config.unused_tags);
unused_detector.detect(&analysis, &mut report);
let undefined_detector = PlcopenUndefinedVarsDetector::new(&self.config.undefined_tags);
undefined_detector.detect(&analysis, &mut report);
let empty_detector = PlcopenEmptyPousDetector::new(&self.config.empty_routines);
empty_detector.detect(&analysis, &mut report);
Ok(report)
}
pub fn analyze_controller(&self, controller: &Controller) -> Result<Report> {
let analysis = analyze_controller(controller);
let mut report = Report::new();
let unused_tags_detector = UnusedTagsDetector::new(&self.config.unused_tags);
unused_tags_detector.detect(controller, &analysis, &mut report);
let undefined_tags_detector = UndefinedTagsDetector::new(&self.config.undefined_tags);
undefined_tags_detector.detect(controller, &analysis, &mut report);
let empty_routines_detector = EmptyRoutinesDetector::new(&self.config.empty_routines);
empty_routines_detector.detect(controller, &analysis, &mut report);
let unused_aois_detector = UnusedAoisDetector::new(&self.config.unused_aois);
unused_aois_detector.detect(&analysis, &mut report);
let unused_datatypes_detector = UnusedDataTypesDetector::new(&self.config.unused_datatypes);
unused_datatypes_detector.detect(controller, &mut report);
let complexity_detector = ComplexityDetector::new(&self.config.complexity);
complexity_detector.detect(&analysis, &mut report);
let nesting_detector = NestingDetector::new(&self.config.nesting);
nesting_detector.detect(&analysis, &mut report);
Ok(report)
}
pub fn get_stats_file(&self, path: &Path) -> Result<ParseStats> {
let project = LoadedProject::from_file(path)?;
self.get_stats(&project)
}
pub fn get_stats(&self, project: &LoadedProject) -> Result<ParseStats> {
if let Some(ref controller) = project.l5x_controller {
let analysis = analyze_controller(controller);
return Ok(analysis.stats);
}
Ok(ParseStats::default())
}
pub fn get_plcopen_stats(&self, project: &LoadedProject) -> Result<PlcopenStats> {
if let Some(ref plcopen) = project.plcopen_project {
let analysis = analyze_plcopen_project(plcopen);
return Ok(analysis.stats);
}
Ok(PlcopenStats::default())
}
}
impl Default for RuleDetector {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_detector_default() {
let detector = RuleDetector::new();
assert!(detector.config().unused_tags.enabled);
}
#[test]
fn test_detector_with_config() {
let mut config = RuleConfig::default();
config.unused_tags.enabled = false;
let detector = RuleDetector::with_config(config);
assert!(!detector.config().unused_tags.enabled);
}
#[test]
fn test_analyze_l5x() {
let xml = r#"<?xml version="1.0"?>
<RSLogix5000Content SchemaRevision="1.0" SoftwareRevision="32.00">
<Controller Name="TestController">
<Tags>
<Tag Name="Unused" DataType="BOOL"/>
</Tags>
<Programs>
<Program Name="MainProgram">
<Routines>
<Routine Name="MainRoutine" Type="RLL">
<RLLContent>
<Rung Number="0">
<Text>NOP();</Text>
</Rung>
</RLLContent>
</Routine>
</Routines>
</Program>
</Programs>
</Controller>
</RSLogix5000Content>"#;
let project = LoadedProject::from_str(xml, None).expect("Should parse");
let detector = RuleDetector::new();
let report = detector.analyze(&project).expect("Should analyze");
assert!(!report.rules.is_empty());
}
#[test]
fn test_analyze_plcopen() {
let xml = r#"<?xml version="1.0"?>
<project xmlns="http://www.plcopen.org/xml/tc6_0200">
<fileHeader companyName="Test" productName="TestProject" productVersion="1.0" creationDateTime="2024-01-01T00:00:00"/>
<contentHeader name="Test"/>
<types>
<pous>
<pou name="Main" pouType="program"/>
</pous>
</types>
</project>"#;
let project = LoadedProject::from_str(xml, None).expect("Should parse");
let detector = RuleDetector::new();
let report = detector.analyze(&project).expect("Should analyze");
assert!(report.rules.iter().any(|s| s.identifier == "Main"));
}
}