raz_core/providers/
leptos.rs

1//! Leptos framework provider for web development commands
2//!
3//! Provides intelligent command suggestions for Leptos projects including
4//! development server, building, testing, and deployment commands.
5
6use crate::{
7    Command, CommandBuilder, CommandCategory, CommandProvider, ProjectContext, ProjectType,
8    RazResult, SymbolKind,
9};
10use async_trait::async_trait;
11
12/// Provider for Leptos web framework commands
13pub struct LeptosProvider {
14    priority: u8,
15}
16
17impl LeptosProvider {
18    pub fn new() -> Self {
19        Self {
20            priority: 90, // High priority for Leptos projects
21        }
22    }
23}
24
25#[async_trait]
26impl CommandProvider for LeptosProvider {
27    fn name(&self) -> &str {
28        "leptos"
29    }
30
31    fn priority(&self) -> u8 {
32        self.priority
33    }
34
35    fn can_handle(&self, context: &ProjectContext) -> bool {
36        // Check if this is a Leptos project
37        match &context.project_type {
38            ProjectType::Leptos => true,
39            ProjectType::Mixed(frameworks) => frameworks.contains(&ProjectType::Leptos),
40            _ => {
41                // Also check dependencies for leptos
42                context.dependencies.iter().any(|dep| {
43                    dep.name == "leptos" || dep.name == "leptos_axum" || dep.name == "leptos_actix"
44                })
45            }
46        }
47    }
48
49    async fn commands(&self, context: &ProjectContext) -> RazResult<Vec<Command>> {
50        let mut commands = Vec::new();
51
52        // Development commands
53        commands.extend(self.development_commands(context));
54
55        // Build commands
56        commands.extend(self.build_commands(context));
57
58        // Testing commands
59        commands.extend(self.test_commands(context));
60
61        // Context-aware commands based on cursor position
62        commands.extend(self.context_aware_commands(context));
63
64        // Deployment commands
65        commands.extend(self.deployment_commands(context));
66
67        Ok(commands)
68    }
69}
70
71impl LeptosProvider {
72    /// Development server and watch commands
73    fn development_commands(&self, context: &ProjectContext) -> Vec<Command> {
74        vec![
75            CommandBuilder::new("leptos-watch", "cargo-leptos")
76                .label("Leptos Dev Watch")
77                .description("Development server with auto-reload (recommended for development)")
78                .arg("watch")
79                .category(CommandCategory::Run)
80                .priority(95)
81                .tag("dev")
82                .tag("watch")
83                .tag("leptos")
84                .cwd(context.workspace_root.clone())
85                .estimated_duration(5)
86                .build(),
87            CommandBuilder::new("leptos-watch-hot", "cargo-leptos")
88                .label("Leptos Hot Reload")
89                .description("Development with hot-reload (requires nightly)")
90                .arg("watch")
91                .arg("--hot-reload")
92                .category(CommandCategory::Run)
93                .priority(92)
94                .tag("dev")
95                .tag("hot-reload")
96                .tag("leptos")
97                .cwd(context.workspace_root.clone())
98                .estimated_duration(5)
99                .build(),
100            CommandBuilder::new("leptos-serve", "cargo-leptos")
101                .label("Leptos Serve")
102                .description("Serve in hydrate mode (production-like, no auto-reload)")
103                .arg("serve")
104                .category(CommandCategory::Run)
105                .priority(85)
106                .tag("serve")
107                .tag("leptos")
108                .cwd(context.workspace_root.clone())
109                .estimated_duration(5)
110                .build(),
111            CommandBuilder::new("leptos-serve-release", "cargo-leptos")
112                .label("Leptos Serve Release")
113                .description("Serve optimized release build")
114                .arg("serve")
115                .arg("--release")
116                .category(CommandCategory::Run)
117                .priority(80)
118                .tag("serve")
119                .tag("release")
120                .tag("leptos")
121                .cwd(context.workspace_root.clone())
122                .estimated_duration(8)
123                .build(),
124        ]
125    }
126
127    /// Build commands for different targets
128    fn build_commands(&self, context: &ProjectContext) -> Vec<Command> {
129        vec![
130            CommandBuilder::new("leptos-build", "cargo-leptos")
131                .label("Leptos Build")
132                .description("Build Leptos app (both server SSR and client WASM)")
133                .arg("build")
134                .category(CommandCategory::Build)
135                .priority(85)
136                .tag("build")
137                .tag("production")
138                .tag("leptos")
139                .cwd(context.workspace_root.clone())
140                .estimated_duration(30)
141                .build(),
142            CommandBuilder::new("leptos-build-release", "cargo-leptos")
143                .label("Leptos Release Build")
144                .description("Build optimized release version")
145                .arg("build")
146                .arg("--release")
147                .category(CommandCategory::Build)
148                .priority(82)
149                .tag("build")
150                .tag("release")
151                .tag("leptos")
152                .cwd(context.workspace_root.clone())
153                .estimated_duration(60)
154                .build(),
155            CommandBuilder::new("leptos-build-precompress", "cargo-leptos")
156                .label("Leptos Build Compressed")
157                .description("Build release with precompressed assets (gzip & brotli)")
158                .arg("build")
159                .arg("--release")
160                .arg("--precompress")
161                .category(CommandCategory::Build)
162                .priority(80)
163                .tag("build")
164                .tag("release")
165                .tag("production")
166                .tag("leptos")
167                .cwd(context.workspace_root.clone())
168                .estimated_duration(70)
169                .build(),
170        ]
171    }
172
173    /// Testing commands
174    fn test_commands(&self, context: &ProjectContext) -> Vec<Command> {
175        vec![
176            CommandBuilder::new("leptos-test", "cargo-leptos")
177                .label("Leptos Test")
178                .description("Run tests for app, client and server")
179                .arg("test")
180                .category(CommandCategory::Test)
181                .priority(75)
182                .tag("test")
183                .tag("leptos")
184                .cwd(context.workspace_root.clone())
185                .estimated_duration(15)
186                .build(),
187            CommandBuilder::new("leptos-end-to-end", "cargo-leptos")
188                .label("Leptos E2E Tests")
189                .description("Start server and run end-to-end tests")
190                .arg("end-to-end")
191                .category(CommandCategory::Test)
192                .priority(72)
193                .tag("test")
194                .tag("e2e")
195                .tag("leptos")
196                .cwd(context.workspace_root.clone())
197                .estimated_duration(90)
198                .build(),
199            CommandBuilder::new("cargo-test-workspace", "cargo")
200                .label("Workspace Tests")
201                .description("Run all tests in the workspace")
202                .arg("test")
203                .arg("--workspace")
204                .category(CommandCategory::Test)
205                .priority(70)
206                .tag("test")
207                .tag("workspace")
208                .tag("leptos")
209                .cwd(context.workspace_root.clone())
210                .estimated_duration(30)
211                .build(),
212        ]
213    }
214
215    /// Context-aware commands based on cursor position and file content
216    fn context_aware_commands(&self, context: &ProjectContext) -> Vec<Command> {
217        let mut commands = Vec::new();
218
219        if let Some(file_context) = &context.current_file {
220            // Commands based on current symbol
221            if let Some(symbol) = &file_context.cursor_symbol {
222                match symbol.kind {
223                    SymbolKind::Function => {
224                        if symbol.name.starts_with("test_")
225                            || symbol.modifiers.contains(&"test".to_string())
226                        {
227                            // Test-specific commands
228                            commands.push(
229                                CommandBuilder::new("cargo-test-current", "cargo")
230                                    .label("Test Current Function")
231                                    .description(format!("Run test function: {}", symbol.name))
232                                    .arg("test")
233                                    .arg(&symbol.name)
234                                    .arg("--")
235                                    .arg("--nocapture")
236                                    .category(CommandCategory::Test)
237                                    .priority(90)
238                                    .tag("test")
239                                    .tag("current")
240                                    .tag("leptos")
241                                    .cwd(context.workspace_root.clone())
242                                    .estimated_duration(5)
243                                    .build(),
244                            );
245                        }
246
247                        // Component-related commands for functions that look like Leptos components
248                        if symbol.name.chars().next().is_some_and(|c| c.is_uppercase()) {
249                            commands.push(
250                                CommandBuilder::new("leptos-dev-component", "cargo-leptos")
251                                    .label("Dev with Component Focus")
252                                    .description(format!(
253                                        "Develop focusing on component: {}",
254                                        symbol.name
255                                    ))
256                                    .arg("serve")
257                                    .category(CommandCategory::Run)
258                                    .priority(85)
259                                    .tag("component")
260                                    .tag("dev")
261                                    .tag("leptos")
262                                    .cwd(context.workspace_root.clone())
263                                    .estimated_duration(10)
264                                    .build(),
265                            );
266                        }
267                    }
268                    SymbolKind::Struct => {
269                        // Struct-related commands (possibly state management)
270                        commands.push(
271                            CommandBuilder::new("leptos-check-struct", "cargo")
272                                .label("Check Struct Usage")
273                                .description(format!("Check usage of struct: {}", symbol.name))
274                                .arg("check")
275                                .arg("--message-format=json")
276                                .category(CommandCategory::Lint)
277                                .priority(70)
278                                .tag("check")
279                                .tag("struct")
280                                .tag("leptos")
281                                .cwd(context.workspace_root.clone())
282                                .estimated_duration(8)
283                                .build(),
284                        );
285                    }
286                    _ => {}
287                }
288            }
289
290            // File-based commands
291            if file_context.path.to_string_lossy().contains("component") {
292                commands.push(
293                    CommandBuilder::new("leptos-build-check", "cargo-leptos")
294                        .label("Build & Check")
295                        .description("Build and check component compilation")
296                        .arg("build")
297                        .category(CommandCategory::Build)
298                        .priority(80)
299                        .tag("components")
300                        .tag("build")
301                        .tag("leptos")
302                        .cwd(context.workspace_root.clone())
303                        .estimated_duration(20)
304                        .build(),
305                );
306            }
307        }
308
309        commands
310    }
311
312    /// Deployment and production commands
313    fn deployment_commands(&self, context: &ProjectContext) -> Vec<Command> {
314        vec![
315            CommandBuilder::new("leptos-new", "cargo-leptos")
316                .label("New Leptos Project")
317                .description("Create a new Leptos project with wizard")
318                .arg("new")
319                .category(CommandCategory::Generate)
320                .priority(70)
321                .tag("new")
322                .tag("generate")
323                .tag("leptos")
324                .cwd(context.workspace_root.clone())
325                .estimated_duration(30)
326                .build(),
327            CommandBuilder::new("leptos-clean", "cargo")
328                .label("Clean Leptos Build")
329                .description("Clean build artifacts and temp files")
330                .arg("clean")
331                .category(CommandCategory::Clean)
332                .priority(60)
333                .tag("clean")
334                .tag("leptos")
335                .cwd(context.workspace_root.clone())
336                .estimated_duration(5)
337                .build(),
338        ]
339    }
340}
341
342impl Default for LeptosProvider {
343    fn default() -> Self {
344        Self::new()
345    }
346}
347
348#[cfg(test)]
349mod tests {
350    use super::*;
351    use crate::{BuildTarget, Dependency, ProjectType, TargetType, WorkspaceMember};
352    use std::collections::HashMap;
353    use std::path::PathBuf;
354
355    fn create_leptos_context() -> ProjectContext {
356        ProjectContext {
357            workspace_root: PathBuf::from("/test"),
358            current_file: None,
359            cursor_position: None,
360            project_type: ProjectType::Leptos,
361            dependencies: vec![Dependency {
362                name: "leptos".to_string(),
363                version: "0.6".to_string(),
364                features: vec!["csr".to_string()],
365                optional: false,
366                dev_dependency: false,
367            }],
368            workspace_members: vec![WorkspaceMember {
369                name: "leptos-app".to_string(),
370                path: PathBuf::from("/test"),
371                package_type: ProjectType::Leptos,
372            }],
373            build_targets: vec![BuildTarget {
374                name: "main".to_string(),
375                target_type: TargetType::Binary,
376                path: PathBuf::from("/test/src/main.rs"),
377            }],
378            active_features: vec!["csr".to_string()],
379            env_vars: HashMap::new(),
380        }
381    }
382
383    #[tokio::test]
384    async fn test_leptos_provider_can_handle() {
385        let provider = LeptosProvider::new();
386        let context = create_leptos_context();
387
388        assert!(provider.can_handle(&context));
389        assert_eq!(provider.name(), "leptos");
390        assert_eq!(provider.priority(), 90);
391    }
392
393    #[tokio::test]
394    async fn test_leptos_commands_generation() {
395        let provider = LeptosProvider::new();
396        let context = create_leptos_context();
397
398        let commands = provider.commands(&context).await.unwrap();
399
400        assert!(!commands.is_empty());
401
402        // Should have development commands
403        assert!(commands.iter().any(|c| c.id == "leptos-watch"));
404        assert!(commands.iter().any(|c| c.id == "leptos-serve"));
405
406        // Should have build commands
407        assert!(commands.iter().any(|c| c.id == "leptos-build"));
408        assert!(commands.iter().any(|c| c.id == "leptos-build-release"));
409
410        // Should have test commands
411        assert!(commands.iter().any(|c| c.id == "leptos-test"));
412
413        // Should have deployment/utility commands
414        assert!(commands.iter().any(|c| c.id == "leptos-new"));
415    }
416
417    #[tokio::test]
418    async fn test_leptos_command_priorities() {
419        let provider = LeptosProvider::new();
420        let context = create_leptos_context();
421
422        let commands = provider.commands(&context).await.unwrap();
423
424        // Development watch should have highest priority
425        let watch_cmd = commands.iter().find(|c| c.id == "leptos-watch").unwrap();
426        assert_eq!(watch_cmd.priority, 95);
427
428        // All commands should have leptos tag
429        assert!(
430            commands
431                .iter()
432                .all(|c| c.tags.contains(&"leptos".to_string()))
433        );
434    }
435
436    #[tokio::test]
437    async fn test_leptos_non_leptos_project() {
438        let provider = LeptosProvider::new();
439        let mut context = create_leptos_context();
440        context.project_type = ProjectType::Binary;
441        context.dependencies.clear();
442
443        assert!(!provider.can_handle(&context));
444    }
445
446    #[tokio::test]
447    async fn test_leptos_dependency_detection() {
448        let provider = LeptosProvider::new();
449        let mut context = create_leptos_context();
450        context.project_type = ProjectType::Binary; // Not explicitly Leptos
451        // But has leptos dependency (already set in create_leptos_context)
452
453        assert!(provider.can_handle(&context));
454    }
455}