tailwind_rs_postcss/
engine.rs

1//! PostCSS Engine implementation
2//!
3//! This module provides the core PostCSS processing engine with plugin support,
4//! AST manipulation, and source map generation.
5
6use crate::ast::{CSSNode, CSSRule};
7use crate::parser::{CSSParser, ParseOptions};
8use crate::transformer::{CSSTransformer, TransformOptions};
9use crate::js_bridge::JSBridge;
10use crate::plugin_loader::{PluginLoader, PluginConfig, PluginResult};
11use crate::source_map::{SourceMapGenerator, SourceMap};
12use crate::error::{PostCSSError, Result};
13use serde::{Deserialize, Serialize};
14use std::collections::HashMap;
15use std::sync::Arc;
16use tokio::sync::RwLock;
17
18/// PostCSS processing engine
19#[derive(Debug)]
20pub struct PostCSSEngine {
21    config: PostCSSConfig,
22    parser: CSSParser,
23    transformer: CSSTransformer,
24    js_bridge: Option<JSBridge>,
25    plugin_loader: PluginLoader,
26    source_map_generator: SourceMapGenerator,
27    cache: Arc<RwLock<HashMap<String, ProcessedCSS>>>,
28}
29
30/// PostCSS configuration
31#[derive(Debug, Clone, Serialize, Deserialize)]
32pub struct PostCSSConfig {
33    /// Plugins to use for processing
34    pub plugins: Vec<PluginConfig>,
35    /// Source map generation
36    pub source_map: bool,
37    /// Source map options
38    pub source_map_options: SourceMapOptions,
39    /// Parser options
40    pub parser_options: ParseOptions,
41    /// Transformer options
42    pub transform_options: TransformOptions,
43    /// Performance options
44    pub performance: PerformanceOptions,
45}
46
47/// Source map configuration
48#[derive(Debug, Clone, Serialize, Deserialize)]
49pub struct SourceMapOptions {
50    /// Generate inline source maps
51    pub inline: bool,
52    /// Source map file path
53    pub file: Option<String>,
54    /// Source root
55    pub source_root: Option<String>,
56    /// Include sources content
57    pub sources_content: bool,
58}
59
60/// Performance configuration
61#[derive(Debug, Clone, Serialize, Deserialize)]
62pub struct PerformanceOptions {
63    /// Enable caching
64    pub enable_cache: bool,
65    /// Cache size limit
66    pub cache_size_limit: usize,
67    /// Parallel processing
68    pub parallel_processing: bool,
69    /// Memory optimization
70    pub memory_optimization: bool,
71}
72
73/// Processed CSS result
74#[derive(Debug, Clone)]
75pub struct ProcessedCSS {
76    /// Generated CSS
77    pub css: String,
78    /// Source map (if enabled)
79    pub source_map: Option<SourceMap>,
80    /// Processing warnings
81    pub warnings: Vec<ProcessingWarning>,
82    /// Processing metrics
83    pub metrics: ProcessingMetrics,
84}
85
86/// Processing warning
87#[derive(Debug, Clone)]
88pub struct ProcessingWarning {
89    pub message: String,
90    pub line: Option<usize>,
91    pub column: Option<usize>,
92    pub severity: WarningSeverity,
93}
94
95/// Warning severity levels
96#[derive(Debug, Clone, PartialEq, Eq)]
97pub enum WarningSeverity {
98    Info,
99    Warning,
100    Error,
101}
102
103/// Processing metrics
104#[derive(Debug, Clone)]
105pub struct ProcessingMetrics {
106    pub parse_time: std::time::Duration,
107    pub transform_time: std::time::Duration,
108    pub generate_time: std::time::Duration,
109    pub total_time: std::time::Duration,
110    pub memory_usage: usize,
111    pub rules_processed: usize,
112    pub plugins_executed: usize,
113}
114
115impl Default for PostCSSConfig {
116    fn default() -> Self {
117        Self {
118            plugins: Vec::new(),
119            source_map: true,
120            source_map_options: SourceMapOptions::default(),
121            parser_options: ParseOptions::default(),
122            transform_options: TransformOptions::default(),
123            performance: PerformanceOptions::default(),
124        }
125    }
126}
127
128impl Default for SourceMapOptions {
129    fn default() -> Self {
130        Self {
131            inline: false,
132            file: None,
133            source_root: None,
134            sources_content: true,
135        }
136    }
137}
138
139impl Default for PerformanceOptions {
140    fn default() -> Self {
141        Self {
142            enable_cache: true,
143            cache_size_limit: 1000,
144            parallel_processing: true,
145            memory_optimization: true,
146        }
147    }
148}
149
150impl PostCSSEngine {
151    /// Create a new PostCSS engine with configuration
152    pub fn new(config: PostCSSConfig) -> Result<Self> {
153        let parser = CSSParser::new(config.parser_options.clone());
154        let transformer = CSSTransformer::new(config.transform_options.clone());
155        let plugin_loader = PluginLoader::new();
156        let source_map_generator = SourceMapGenerator::new();
157        
158        // Initialize JavaScript bridge if needed
159        let js_bridge = if config.plugins.iter().any(|p| p.requires_js()) {
160            Some(JSBridge::new()?)
161        } else {
162            None
163        };
164
165        Ok(Self {
166            config,
167            parser,
168            transformer,
169            js_bridge,
170            plugin_loader,
171            source_map_generator,
172            cache: Arc::new(RwLock::new(HashMap::new())),
173        })
174    }
175
176    /// Process CSS input through PostCSS pipeline
177    pub async fn process_css(&self, input: &str) -> Result<ProcessedCSS> {
178        let start_time = std::time::Instant::now();
179        
180        // Check cache first
181        if self.config.performance.enable_cache {
182            if let Some(cached) = self.get_cached_result(input).await {
183                return Ok(cached);
184            }
185        }
186
187        // Parse CSS into AST
188        let parse_start = std::time::Instant::now();
189        let ast = self.parser.parse(input)?;
190        let parse_time = parse_start.elapsed();
191
192        // Apply transformations
193        let transform_start = std::time::Instant::now();
194        let transformed_ast = self.apply_transformations(ast).await?;
195        let transform_time = transform_start.elapsed();
196
197        // Generate output CSS
198        let generate_start = std::time::Instant::now();
199        let css = self.generate_css(&transformed_ast)?;
200        let generate_time = generate_start.elapsed();
201
202        // Generate source map if enabled
203        let source_map = if self.config.source_map {
204            let source_map_options = crate::source_map::SourceMapOptions {
205                inline: self.config.source_map_options.inline,
206                file: self.config.source_map_options.file.clone(),
207                source_root: self.config.source_map_options.source_root.clone(),
208                sources_content: self.config.source_map_options.sources_content,
209            };
210            Some(self.source_map_generator.generate(
211                input,
212                &css,
213                &source_map_options,
214            )?)
215        } else {
216            None
217        };
218
219        let total_time = start_time.elapsed();
220        let metrics = ProcessingMetrics {
221            parse_time,
222            transform_time,
223            generate_time,
224            total_time,
225            memory_usage: self.get_memory_usage(),
226            rules_processed: self.count_rules(&transformed_ast),
227            plugins_executed: self.config.plugins.len(),
228        };
229
230        let result = ProcessedCSS {
231            css,
232            source_map,
233            warnings: Vec::new(), // TODO: Collect warnings during processing
234            metrics,
235        };
236
237        // Cache result if enabled
238        if self.config.performance.enable_cache {
239            self.cache_result(input, &result).await;
240        }
241
242        Ok(result)
243    }
244
245    /// Apply all configured transformations to the AST
246    async fn apply_transformations(&self, mut ast: CSSNode) -> Result<CSSNode> {
247        for plugin_config in &self.config.plugins {
248            let plugin_result = self.plugin_loader.load_plugin(plugin_config).await?;
249            
250            match plugin_result {
251                PluginResult::Native(plugin) => {
252                    ast = plugin.transform(ast)?;
253                }
254                PluginResult::JavaScript(js_plugin) => {
255                    if let Some(js_bridge) = &self.js_bridge {
256                        ast = js_bridge.execute_plugin(&js_plugin.name, ast).await?;
257                    } else {
258                        return Err(PostCSSError::JavaScriptBridgeNotAvailable);
259                    }
260                }
261            }
262        }
263
264        // Apply built-in transformations
265        ast = self.transformer.transform(ast)?;
266
267        Ok(ast)
268    }
269
270    /// Generate CSS from transformed AST
271    fn generate_css(&self, ast: &CSSNode) -> Result<String> {
272        match ast {
273            CSSNode::Stylesheet(rules) => {
274                let mut css = String::new();
275                for rule in rules {
276                    css.push_str(&self.rule_to_css(rule)?);
277                    css.push('\n');
278                }
279                Ok(css)
280            }
281            _ => Err(PostCSSError::InvalidAST("Expected stylesheet".to_string())),
282        }
283    }
284
285    /// Convert a CSS rule to CSS string
286    fn rule_to_css(&self, rule: &CSSRule) -> Result<String> {
287        let mut css = String::new();
288        
289        // Add selector
290        css.push_str(&rule.selector);
291        css.push_str(" {\n");
292        
293        // Add declarations
294        for declaration in &rule.declarations {
295            css.push_str("  ");
296            css.push_str(&declaration.property);
297            css.push_str(": ");
298            css.push_str(&declaration.value);
299            if declaration.important {
300                css.push_str(" !important");
301            }
302            css.push_str(";\n");
303        }
304        
305        css.push('}');
306        Ok(css)
307    }
308
309    /// Get cached result if available
310    async fn get_cached_result(&self, input: &str) -> Option<ProcessedCSS> {
311        let cache = self.cache.read().await;
312        cache.get(input).cloned()
313    }
314
315    /// Cache processing result
316    async fn cache_result(&self, input: &str, result: &ProcessedCSS) {
317        let mut cache = self.cache.write().await;
318        
319        // Check cache size limit
320        if cache.len() >= self.config.performance.cache_size_limit {
321            // Remove oldest entries (simple LRU)
322            let keys_to_remove: Vec<String> = cache.keys().take(cache.len() / 2).cloned().collect();
323            for key in keys_to_remove {
324                cache.remove(&key);
325            }
326        }
327        
328        cache.insert(input.to_string(), result.clone());
329    }
330
331    /// Get current memory usage
332    fn get_memory_usage(&self) -> usize {
333        // Simple memory usage estimation
334        // In a real implementation, this would use proper memory profiling
335        std::mem::size_of::<Self>() + self.cache.try_read().map(|c| c.len() * 1024).unwrap_or(0)
336    }
337
338    /// Count rules in AST
339    fn count_rules(&self, ast: &CSSNode) -> usize {
340        match ast {
341            CSSNode::Stylesheet(rules) => rules.len(),
342            _ => 0,
343        }
344    }
345
346    /// Get engine metrics
347    pub async fn get_metrics(&self) -> EngineMetrics {
348        let cache = self.cache.read().await;
349        EngineMetrics {
350            cache_size: cache.len(),
351            memory_usage: self.get_memory_usage(),
352            plugins_loaded: self.config.plugins.len(),
353            js_bridge_available: self.js_bridge.is_some(),
354        }
355    }
356
357    /// Clear cache
358    pub async fn clear_cache(&self) {
359        let mut cache = self.cache.write().await;
360        cache.clear();
361    }
362}
363
364/// Engine metrics
365#[derive(Debug, Clone)]
366pub struct EngineMetrics {
367    pub cache_size: usize,
368    pub memory_usage: usize,
369    pub plugins_loaded: usize,
370    pub js_bridge_available: bool,
371}
372
373#[cfg(test)]
374mod tests {
375    use super::*;
376
377    #[tokio::test]
378    async fn test_engine_creation() {
379        let config = PostCSSConfig::default();
380        let engine = PostCSSEngine::new(config);
381        assert!(engine.is_ok());
382    }
383
384    #[tokio::test]
385    async fn test_css_processing() {
386        let engine = PostCSSEngine::new(PostCSSConfig::default()).unwrap();
387        let input = ".test { color: red; }";
388        let result = engine.process_css(input).await;
389        assert!(result.is_ok());
390        
391        let css = result.unwrap();
392        assert!(css.css.contains(".test"));
393        assert!(css.css.contains("color: red"));
394    }
395
396    #[tokio::test]
397    async fn test_caching() {
398        let mut config = PostCSSConfig::default();
399        config.performance.enable_cache = true;
400        
401        let engine = PostCSSEngine::new(config).unwrap();
402        let input = ".test { color: red; }";
403        
404        // First processing
405        let result1 = engine.process_css(input).await.unwrap();
406        
407        // Second processing (should use cache)
408        let result2 = engine.process_css(input).await.unwrap();
409        
410        assert_eq!(result1.css, result2.css);
411    }
412}