Skip to main content

ass_core/analysis/
construction.rs

1//! Construction routines for [`ScriptAnalysis`].
2//!
3//! Provides the public `analyze*` constructors along with the private helpers
4//! that resolve styles, analyze events, and run linting while building the
5//! cached analysis result.
6
7use super::{
8    linting, AnalysisConfig, DialogueInfo, LintConfig, ScriptAnalysis, ScriptAnalysisOptions,
9    StyleAnalyzer,
10};
11#[cfg(feature = "plugins")]
12use crate::plugin::ExtensionRegistry;
13use crate::{
14    parser::{Script, Section},
15    Result,
16};
17use alloc::vec::Vec;
18
19impl<'a> ScriptAnalysis<'a> {
20    /// Analyze script with default configuration
21    ///
22    /// Performs comprehensive analysis including linting, style resolution,
23    /// and event analysis. Results are cached for efficient access.
24    /// Analyze ASS script for issues, styles, and content
25    ///
26    /// # Performance
27    ///
28    /// Target <2ms for typical scripts. Uses lazy evaluation for expensive
29    /// operations like Unicode analysis.
30    ///
31    /// # Errors
32    ///
33    /// Returns an error if script analysis fails or contains invalid data.
34    pub fn analyze(script: &'a Script<'a>) -> Result<Self> {
35        #[cfg(feature = "plugins")]
36        return Self::analyze_with_registry(script, None, AnalysisConfig::default());
37        #[cfg(not(feature = "plugins"))]
38        return Self::analyze_with_config(script, AnalysisConfig::default());
39    }
40
41    /// Analyze script with extension registry support
42    ///
43    /// Same as [`analyze`](Self::analyze) but allows custom tag handlers via registry.
44    /// Uses default analysis configuration.
45    ///
46    /// # Arguments
47    ///
48    /// * `script` - Script to analyze
49    /// * `registry` - Optional registry for custom tag handlers
50    ///
51    /// # Errors
52    ///
53    /// Returns an error if script analysis fails or contains invalid data.
54    #[cfg(feature = "plugins")]
55    pub fn analyze_with_registry(
56        script: &'a Script<'a>,
57        registry: Option<&'a ExtensionRegistry>,
58        config: AnalysisConfig,
59    ) -> Result<Self> {
60        Ok(Self::analyze_impl(script, registry, config))
61    }
62
63    /// Analyze script with custom configuration
64    ///
65    /// Allows fine-tuning analysis behavior for specific use cases.
66    ///
67    /// # Errors
68    ///
69    /// Returns an error if script analysis fails or contains invalid data.
70    pub fn analyze_with_config(script: &'a Script<'a>, config: AnalysisConfig) -> Result<Self> {
71        #[cfg(feature = "plugins")]
72        return Ok(Self::analyze_impl(script, None, config));
73        #[cfg(not(feature = "plugins"))]
74        return Ok(Self::analyze_impl_no_plugins(script, config));
75    }
76
77    /// Internal implementation with plugins support
78    #[cfg(feature = "plugins")]
79    fn analyze_impl(
80        script: &'a Script<'a>,
81        registry: Option<&'a ExtensionRegistry>,
82        config: AnalysisConfig,
83    ) -> Self {
84        let mut analysis = Self {
85            script,
86            lint_issues: Vec::new(),
87            resolved_styles: Vec::new(),
88            dialogue_info: Vec::new(),
89            config,
90            registry,
91        };
92
93        analysis.resolve_all_styles();
94        analysis.analyze_events();
95        analysis.run_linting();
96
97        analysis
98    }
99
100    /// Internal implementation without plugins support
101    #[cfg(not(feature = "plugins"))]
102    fn analyze_impl_no_plugins(script: &'a Script<'a>, config: AnalysisConfig) -> Self {
103        let mut analysis = Self {
104            script,
105            lint_issues: Vec::new(),
106            resolved_styles: Vec::new(),
107            dialogue_info: Vec::new(),
108            config,
109        };
110
111        analysis.resolve_all_styles();
112        analysis.analyze_events();
113        analysis.run_linting();
114
115        analysis
116    }
117
118    /// Run linting analysis
119    fn run_linting(&mut self) {
120        let lint_config = LintConfig::default().with_strict_compliance(
121            self.config
122                .options
123                .contains(ScriptAnalysisOptions::STRICT_COMPLIANCE),
124        );
125
126        let mut issues = Vec::new();
127        let rules = linting::rules::BuiltinRules::all_rules();
128
129        for rule in rules {
130            if !lint_config.is_rule_enabled(rule.id()) {
131                continue;
132            }
133
134            let mut rule_issues = rule.check_script(self);
135            rule_issues.retain(|issue| lint_config.should_report_severity(issue.severity()));
136
137            issues.extend(rule_issues);
138
139            if lint_config.max_issues > 0 && issues.len() >= lint_config.max_issues {
140                issues.truncate(lint_config.max_issues);
141                break;
142            }
143        }
144
145        self.lint_issues = issues;
146    }
147
148    /// Resolve all styles with inheritance and overrides
149    pub(super) fn resolve_all_styles(&mut self) {
150        let analyzer = StyleAnalyzer::new(self.script);
151        self.resolved_styles = analyzer.resolved_styles().values().cloned().collect();
152    }
153
154    /// Analyze events for timing, overlaps, and performance
155    pub(super) fn analyze_events(&mut self) {
156        if let Some(Section::Events(events)) = self
157            .script
158            .sections()
159            .iter()
160            .find(|s| matches!(s, Section::Events(_)))
161        {
162            for event in events {
163                #[cfg(feature = "plugins")]
164                let info_result = self.registry.map_or_else(
165                    || DialogueInfo::analyze(event),
166                    |registry| DialogueInfo::analyze_with_registry(event, Some(registry)),
167                );
168
169                #[cfg(not(feature = "plugins"))]
170                let info_result = DialogueInfo::analyze(event);
171
172                if let Ok(info) = info_result {
173                    self.dialogue_info.push(info);
174                }
175            }
176        }
177    }
178}