ass_core/analysis/linting/rules/
performance.rs

1//! Performance issue detection rule for ASS script linting.
2//!
3//! Detects potential performance issues in subtitle scripts that could
4//! impact rendering speed, memory usage, or playback smoothness.
5
6use crate::analysis::{
7    linting::{IssueCategory, IssueSeverity, LintIssue, LintRule},
8    ScriptAnalysis,
9};
10use alloc::{format, string::ToString, vec::Vec};
11
12/// Rule for detecting potential performance issues in subtitle scripts
13///
14/// Analyzes scripts for patterns that may negatively impact rendering
15/// performance, memory usage, or playback smoothness. Helps identify
16/// optimization opportunities in large or complex subtitle files.
17///
18/// # Performance Checks
19///
20/// - Event count: Warns when script has excessive number of events
21/// - Complex text: Detects events with very long text content
22/// - Frequent styling: Identifies excessive use of override tags
23///
24/// # Performance
25///
26/// - Time complexity: O(n) for n events
27/// - Memory: O(1) additional space
28/// - Target: <1ms for typical scripts with 1000+ events
29///
30/// # Example
31///
32/// ```rust
33/// use ass_core::analysis::linting::rules::performance::PerformanceRule;
34/// use ass_core::analysis::linting::LintRule;
35/// use ass_core::{Script, ScriptAnalysis};
36///
37/// let script = Script::parse(r#"
38/// [Events]
39/// Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
40/// "#)?; // Script with many events would trigger warnings
41///
42/// let analysis = ScriptAnalysis::analyze(&script)?;
43/// let rule = PerformanceRule;
44/// let issues = rule.check_script(&analysis);
45/// # Ok::<(), Box<dyn std::error::Error>>(())
46/// ```
47pub struct PerformanceRule;
48
49impl LintRule for PerformanceRule {
50    fn id(&self) -> &'static str {
51        "performance"
52    }
53
54    fn name(&self) -> &'static str {
55        "Performance"
56    }
57
58    fn description(&self) -> &'static str {
59        "Detects potential performance issues in the script"
60    }
61
62    fn default_severity(&self) -> IssueSeverity {
63        IssueSeverity::Hint
64    }
65
66    fn category(&self) -> IssueCategory {
67        IssueCategory::Performance
68    }
69
70    fn check_script(&self, analysis: &ScriptAnalysis) -> Vec<LintIssue> {
71        let mut issues = Vec::new();
72
73        let dialogue_info = analysis.dialogue_info();
74        self.check_event_count(&mut issues, dialogue_info.len());
75        self.check_complex_events(&mut issues, dialogue_info);
76
77        issues
78    }
79}
80
81impl PerformanceRule {
82    /// Check for excessive number of events
83    fn check_event_count(&self, issues: &mut Vec<LintIssue>, event_count: usize) {
84        if event_count > 1000 {
85            let issue = LintIssue::new(
86                self.default_severity(),
87                IssueCategory::Performance,
88                self.id(),
89                format!("Script has {event_count} events, consider optimization"),
90            )
91            .with_description("Large number of events may impact rendering performance".to_string())
92            .with_suggested_fix(
93                "Consider splitting into multiple files or optimizing timing".to_string(),
94            );
95
96            issues.push(issue);
97        }
98    }
99
100    /// Check for performance-impacting patterns in individual events
101    fn check_complex_events(
102        &self,
103        issues: &mut Vec<LintIssue>,
104        dialogue_info: &[crate::analysis::DialogueInfo],
105    ) {
106        for info in dialogue_info {
107            let text_analysis = info.text_analysis();
108            let text_length = text_analysis.char_count();
109
110            if text_length > 500 {
111                let issue = LintIssue::new(
112                    self.default_severity(),
113                    IssueCategory::Performance,
114                    self.id(),
115                    format!("Event has very long text ({text_length} characters)"),
116                )
117                .with_description(
118                    "Very long event text may impact rendering performance".to_string(),
119                )
120                .with_suggested_fix(
121                    "Consider splitting long text into multiple events".to_string(),
122                );
123
124                issues.push(issue);
125            }
126
127            let override_count = text_analysis.override_tags().len();
128            if override_count > 20 {
129                let issue = LintIssue::new(
130                    self.default_severity(),
131                    IssueCategory::Performance,
132                    self.id(),
133                    format!("Event has many override tags ({override_count} blocks)"),
134                )
135                .with_description(
136                    "Excessive override tags may impact rendering performance".to_string(),
137                )
138                .with_suggested_fix(
139                    "Consider using styles instead of many override tags".to_string(),
140                );
141
142                issues.push(issue);
143            }
144        }
145    }
146}
147
148#[cfg(test)]
149mod tests {
150    use super::*;
151    #[cfg(not(feature = "std"))]
152    use alloc::string::String;
153
154    #[test]
155    fn rule_metadata_correct() {
156        let rule = PerformanceRule;
157        assert_eq!(rule.id(), "performance");
158        assert_eq!(rule.name(), "Performance");
159        assert_eq!(
160            rule.description(),
161            "Detects potential performance issues in the script"
162        );
163        assert_eq!(rule.default_severity(), IssueSeverity::Hint);
164        assert_eq!(rule.category(), IssueCategory::Performance);
165    }
166
167    #[test]
168    fn empty_script_no_issues() {
169        let script_text = "[Script Info]\nTitle: Test";
170        let script = crate::parser::Script::parse(script_text).unwrap();
171        let analysis = ScriptAnalysis::analyze(&script).unwrap();
172
173        let rule = PerformanceRule;
174        let issues = rule.check_script(&analysis);
175
176        assert!(issues.is_empty());
177    }
178
179    #[test]
180    fn small_script_no_issues() {
181        let script_text = r"[Events]
182Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
183Dialogue: 0,0:00:00.00,0:00:05.00,Default,,0,0,0,,Short text
184Dialogue: 0,0:00:05.00,0:00:10.00,Default,,0,0,0,,Another short text";
185
186        let script = crate::parser::Script::parse(script_text).unwrap();
187        let analysis = ScriptAnalysis::analyze(&script).unwrap();
188        let rule = PerformanceRule;
189        let issues = rule.check_script(&analysis);
190
191        assert!(issues.is_empty());
192    }
193
194    #[test]
195    fn no_events_section_no_issues() {
196        let script_text = r"[Script Info]
197Title: Test
198
199[V4+ Styles]
200Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding
201Style: Default,Arial,20,&H00FFFFFF&,&H000000FF&,&H00000000&,&H00000000&,0,0,0,0,100,100,0,0,1,2,0,2,10,10,10,1";
202
203        let script = crate::parser::Script::parse(script_text).unwrap();
204        let analysis = ScriptAnalysis::analyze(&script).unwrap();
205        let rule = PerformanceRule;
206        let issues = rule.check_script(&analysis);
207
208        assert!(issues.is_empty());
209    }
210
211    #[test]
212    fn long_text_event_detected() {
213        let long_text = "a".repeat(600);
214        let script_text = format!(
215            r"[Events]
216Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
217Dialogue: 0,0:00:00.00,0:00:05.00,Default,,0,0,0,,{long_text}"
218        );
219
220        let script = crate::parser::Script::parse(&script_text).unwrap();
221        let analysis = ScriptAnalysis::analyze(&script).unwrap();
222        let rule = PerformanceRule;
223        let issues = rule.check_script(&analysis);
224
225        assert!(!issues.is_empty());
226        assert!(issues
227            .iter()
228            .any(|issue| issue.message().contains("long text")));
229    }
230
231    #[test]
232    fn many_override_tags_detected() {
233        let mut text_with_tags = String::new();
234        for i in 0..25 {
235            use core::fmt::Write;
236            write!(text_with_tags, "{{\\i{}}}text", i % 2).unwrap();
237        }
238
239        let script_text = format!(
240            r"[Events]
241Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
242Dialogue: 0,0:00:00.00,0:00:05.00,Default,,0,0,0,,{text_with_tags}"
243        );
244
245        let script = crate::parser::Script::parse(&script_text).unwrap();
246        let analysis = ScriptAnalysis::analyze(&script).unwrap();
247        let rule = PerformanceRule;
248        let issues = rule.check_script(&analysis);
249
250        assert!(!issues.is_empty());
251        assert!(issues
252            .iter()
253            .any(|issue| issue.message().contains("override tags")));
254    }
255}