ass_core/analysis/linting/rules/
performance.rs1use crate::analysis::{
7 linting::{IssueCategory, IssueSeverity, LintIssue, LintRule},
8 ScriptAnalysis,
9};
10use alloc::{format, string::ToString, vec::Vec};
11
12pub 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 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 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}