raz_core/providers/
yew.rs

1//! Yew framework provider for web development commands
2//!
3//! Provides intelligent command suggestions for Yew 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 Yew web framework commands
13pub struct YewProvider {
14    priority: u8,
15}
16
17impl YewProvider {
18    pub fn new() -> Self {
19        Self {
20            priority: 90, // High priority for Yew projects
21        }
22    }
23}
24
25#[async_trait]
26impl CommandProvider for YewProvider {
27    fn name(&self) -> &str {
28        "yew"
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 Yew project
37        match &context.project_type {
38            ProjectType::Yew => true,
39            ProjectType::Mixed(frameworks) => frameworks.contains(&ProjectType::Yew),
40            _ => {
41                // Also check dependencies for yew
42                context.dependencies.iter().any(|dep| {
43                    dep.name == "yew" || dep.name == "yew-router" || dep.name == "yew-hooks"
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 and utility commands
65        commands.extend(self.deployment_commands(context));
66
67        Ok(commands)
68    }
69}
70
71impl YewProvider {
72    /// Development server and watch commands
73    fn development_commands(&self, context: &ProjectContext) -> Vec<Command> {
74        vec![
75            CommandBuilder::new("yew-serve", "trunk")
76                .label("Yew Dev Server")
77                .description("Development server with auto-reload (recommended for development)")
78                .arg("serve")
79                .category(CommandCategory::Run)
80                .priority(95)
81                .tag("dev")
82                .tag("serve")
83                .tag("yew")
84                .cwd(context.workspace_root.clone())
85                .estimated_duration(5)
86                .build(),
87            CommandBuilder::new("yew-serve-open", "trunk")
88                .label("Yew Dev Server + Open")
89                .description("Development server with auto-reload and open browser")
90                .arg("serve")
91                .arg("--open")
92                .category(CommandCategory::Run)
93                .priority(92)
94                .tag("dev")
95                .tag("serve")
96                .tag("open")
97                .tag("yew")
98                .cwd(context.workspace_root.clone())
99                .estimated_duration(5)
100                .build(),
101            CommandBuilder::new("yew-serve-port", "trunk")
102                .label("Yew Dev Server (Port 8080)")
103                .description("Development server on custom port 8080")
104                .arg("serve")
105                .arg("--port")
106                .arg("8080")
107                .category(CommandCategory::Run)
108                .priority(85)
109                .tag("dev")
110                .tag("serve")
111                .tag("port")
112                .tag("yew")
113                .cwd(context.workspace_root.clone())
114                .estimated_duration(5)
115                .build(),
116            CommandBuilder::new("yew-watch", "trunk")
117                .label("Yew Watch Build")
118                .description("Watch for changes and rebuild (no server)")
119                .arg("watch")
120                .category(CommandCategory::Build)
121                .priority(80)
122                .tag("watch")
123                .tag("build")
124                .tag("yew")
125                .cwd(context.workspace_root.clone())
126                .estimated_duration(5)
127                .build(),
128        ]
129    }
130
131    /// Build commands for different targets
132    fn build_commands(&self, context: &ProjectContext) -> Vec<Command> {
133        vec![
134            CommandBuilder::new("yew-build", "trunk")
135                .label("Yew Build")
136                .description("Build Yew app for development")
137                .arg("build")
138                .category(CommandCategory::Build)
139                .priority(85)
140                .tag("build")
141                .tag("yew")
142                .cwd(context.workspace_root.clone())
143                .estimated_duration(30)
144                .build(),
145            CommandBuilder::new("yew-build-release", "trunk")
146                .label("Yew Release Build")
147                .description("Build optimized release version")
148                .arg("build")
149                .arg("--release")
150                .category(CommandCategory::Build)
151                .priority(82)
152                .tag("build")
153                .tag("release")
154                .tag("yew")
155                .cwd(context.workspace_root.clone())
156                .estimated_duration(60)
157                .build(),
158            CommandBuilder::new("yew-build-public-url", "trunk")
159                .label("Yew Build with Public URL")
160                .description("Build with custom public URL for deployment")
161                .arg("build")
162                .arg("--release")
163                .arg("--public-url")
164                .arg("/app/")
165                .category(CommandCategory::Build)
166                .priority(70)
167                .tag("build")
168                .tag("release")
169                .tag("deploy")
170                .tag("yew")
171                .cwd(context.workspace_root.clone())
172                .estimated_duration(65)
173                .build(),
174            CommandBuilder::new("yew-build-dist", "trunk")
175                .label("Yew Build to Custom Dist")
176                .description("Build to custom dist directory")
177                .arg("build")
178                .arg("--release")
179                .arg("--dist")
180                .arg("build")
181                .category(CommandCategory::Build)
182                .priority(65)
183                .tag("build")
184                .tag("release")
185                .tag("custom")
186                .tag("yew")
187                .cwd(context.workspace_root.clone())
188                .estimated_duration(65)
189                .build(),
190        ]
191    }
192
193    /// Testing commands
194    fn test_commands(&self, context: &ProjectContext) -> Vec<Command> {
195        vec![
196            CommandBuilder::new("yew-test", "cargo")
197                .label("Yew Tests")
198                .description("Run Rust tests for Yew components")
199                .arg("test")
200                .category(CommandCategory::Test)
201                .priority(75)
202                .tag("test")
203                .tag("yew")
204                .cwd(context.workspace_root.clone())
205                .estimated_duration(15)
206                .build(),
207            CommandBuilder::new("yew-test-wasm", "wasm-pack")
208                .label("Yew WASM Tests")
209                .description("Run tests in WASM environment")
210                .arg("test")
211                .arg("--headless")
212                .arg("--chrome")
213                .category(CommandCategory::Test)
214                .priority(72)
215                .tag("test")
216                .tag("wasm")
217                .tag("browser")
218                .tag("yew")
219                .cwd(context.workspace_root.clone())
220                .estimated_duration(30)
221                .build(),
222            CommandBuilder::new("cargo-test-workspace", "cargo")
223                .label("Workspace Tests")
224                .description("Run all tests in the workspace")
225                .arg("test")
226                .arg("--workspace")
227                .category(CommandCategory::Test)
228                .priority(70)
229                .tag("test")
230                .tag("workspace")
231                .tag("yew")
232                .cwd(context.workspace_root.clone())
233                .estimated_duration(30)
234                .build(),
235        ]
236    }
237
238    /// Context-aware commands based on cursor position and file content
239    fn context_aware_commands(&self, context: &ProjectContext) -> Vec<Command> {
240        let mut commands = Vec::new();
241
242        if let Some(file_context) = &context.current_file {
243            // Commands based on current symbol
244            if let Some(symbol) = &file_context.cursor_symbol {
245                match symbol.kind {
246                    SymbolKind::Function => {
247                        if symbol.name.starts_with("test_")
248                            || symbol.modifiers.contains(&"test".to_string())
249                        {
250                            // Test-specific commands
251                            commands.push(
252                                CommandBuilder::new("cargo-test-current", "cargo")
253                                    .label("Test Current Function")
254                                    .description(format!("Run test function: {}", symbol.name))
255                                    .arg("test")
256                                    .arg(&symbol.name)
257                                    .arg("--")
258                                    .arg("--nocapture")
259                                    .category(CommandCategory::Test)
260                                    .priority(90)
261                                    .tag("test")
262                                    .tag("current")
263                                    .tag("yew")
264                                    .cwd(context.workspace_root.clone())
265                                    .estimated_duration(5)
266                                    .build(),
267                            );
268                        }
269
270                        // Component-related commands for functions that look like Yew components
271                        if symbol.name.chars().next().is_some_and(|c| c.is_uppercase()) {
272                            commands.push(
273                                CommandBuilder::new("yew-dev-component", "trunk")
274                                    .label("Dev with Component Focus")
275                                    .description(format!(
276                                        "Develop focusing on component: {}",
277                                        symbol.name
278                                    ))
279                                    .arg("serve")
280                                    .arg("--open")
281                                    .category(CommandCategory::Run)
282                                    .priority(88)
283                                    .tag("component")
284                                    .tag("dev")
285                                    .tag("yew")
286                                    .cwd(context.workspace_root.clone())
287                                    .estimated_duration(8)
288                                    .build(),
289                            );
290                        }
291                    }
292                    SymbolKind::Struct => {
293                        // Struct-related commands (possibly component props or state)
294                        commands.push(
295                            CommandBuilder::new("yew-check-struct", "cargo")
296                                .label("Check Struct Usage")
297                                .description(format!("Check usage of struct: {}", symbol.name))
298                                .arg("check")
299                                .arg("--message-format=json")
300                                .category(CommandCategory::Lint)
301                                .priority(70)
302                                .tag("check")
303                                .tag("struct")
304                                .tag("yew")
305                                .cwd(context.workspace_root.clone())
306                                .estimated_duration(8)
307                                .build(),
308                        );
309                    }
310                    _ => {}
311                }
312            }
313
314            // File-based commands
315            if file_context.path.to_string_lossy().contains("component") {
316                commands.push(
317                    CommandBuilder::new("yew-build-check", "trunk")
318                        .label("Build & Check")
319                        .description("Build and check component compilation")
320                        .arg("build")
321                        .category(CommandCategory::Build)
322                        .priority(80)
323                        .tag("components")
324                        .tag("build")
325                        .tag("yew")
326                        .cwd(context.workspace_root.clone())
327                        .estimated_duration(20)
328                        .build(),
329                );
330            }
331        }
332
333        commands
334    }
335
336    /// Deployment and utility commands
337    fn deployment_commands(&self, context: &ProjectContext) -> Vec<Command> {
338        vec![
339            CommandBuilder::new("yew-clean", "trunk")
340                .label("Clean Yew Build")
341                .description("Clean trunk build artifacts")
342                .arg("clean")
343                .category(CommandCategory::Clean)
344                .priority(60)
345                .tag("clean")
346                .tag("yew")
347                .cwd(context.workspace_root.clone())
348                .estimated_duration(2)
349                .build(),
350            CommandBuilder::new("cargo-clean", "cargo")
351                .label("Clean Cargo Build")
352                .description("Clean cargo build artifacts")
353                .arg("clean")
354                .category(CommandCategory::Clean)
355                .priority(55)
356                .tag("clean")
357                .tag("cargo")
358                .tag("yew")
359                .cwd(context.workspace_root.clone())
360                .estimated_duration(5)
361                .build(),
362            CommandBuilder::new("yew-config", "trunk")
363                .label("Trunk Config Check")
364                .description("Check Trunk.toml configuration")
365                .arg("config")
366                .arg("show")
367                .category(CommandCategory::Generate)
368                .priority(50)
369                .tag("config")
370                .tag("trunk")
371                .tag("yew")
372                .cwd(context.workspace_root.clone())
373                .estimated_duration(2)
374                .build(),
375        ]
376    }
377}
378
379impl Default for YewProvider {
380    fn default() -> Self {
381        Self::new()
382    }
383}
384
385#[cfg(test)]
386mod tests {
387    use super::*;
388    use crate::{BuildTarget, Dependency, ProjectType, TargetType, WorkspaceMember};
389    use std::collections::HashMap;
390    use std::path::PathBuf;
391
392    fn create_yew_context() -> ProjectContext {
393        ProjectContext {
394            workspace_root: PathBuf::from("/test"),
395            current_file: None,
396            cursor_position: None,
397            project_type: ProjectType::Yew,
398            dependencies: vec![Dependency {
399                name: "yew".to_string(),
400                version: "0.21".to_string(),
401                features: vec!["web".to_string()],
402                optional: false,
403                dev_dependency: false,
404            }],
405            workspace_members: vec![WorkspaceMember {
406                name: "yew-app".to_string(),
407                path: PathBuf::from("/test"),
408                package_type: ProjectType::Yew,
409            }],
410            build_targets: vec![BuildTarget {
411                name: "main".to_string(),
412                target_type: TargetType::Binary,
413                path: PathBuf::from("/test/src/main.rs"),
414            }],
415            active_features: vec!["web".to_string()],
416            env_vars: HashMap::new(),
417        }
418    }
419
420    #[tokio::test]
421    async fn test_yew_provider_can_handle() {
422        let provider = YewProvider::new();
423        let context = create_yew_context();
424
425        assert!(provider.can_handle(&context));
426        assert_eq!(provider.name(), "yew");
427        assert_eq!(provider.priority(), 90);
428    }
429
430    #[tokio::test]
431    async fn test_yew_commands_generation() {
432        let provider = YewProvider::new();
433        let context = create_yew_context();
434
435        let commands = provider.commands(&context).await.unwrap();
436
437        assert!(!commands.is_empty());
438
439        // Should have development commands
440        assert!(commands.iter().any(|c| c.id == "yew-serve"));
441        assert!(commands.iter().any(|c| c.id == "yew-serve-open"));
442
443        // Should have build commands
444        assert!(commands.iter().any(|c| c.id == "yew-build"));
445        assert!(commands.iter().any(|c| c.id == "yew-build-release"));
446
447        // Should have test commands
448        assert!(commands.iter().any(|c| c.id == "yew-test"));
449
450        // Should have utility commands
451        assert!(commands.iter().any(|c| c.id == "yew-clean"));
452    }
453
454    #[tokio::test]
455    async fn test_yew_command_priorities() {
456        let provider = YewProvider::new();
457        let context = create_yew_context();
458
459        let commands = provider.commands(&context).await.unwrap();
460
461        // Development serve should have highest priority
462        let serve_cmd = commands.iter().find(|c| c.id == "yew-serve").unwrap();
463        assert_eq!(serve_cmd.priority, 95);
464
465        // All commands should have yew tag
466        assert!(commands.iter().all(|c| c.tags.contains(&"yew".to_string())));
467    }
468
469    #[tokio::test]
470    async fn test_yew_non_yew_project() {
471        let provider = YewProvider::new();
472        let mut context = create_yew_context();
473        context.project_type = ProjectType::Binary;
474        context.dependencies.clear();
475
476        assert!(!provider.can_handle(&context));
477    }
478
479    #[tokio::test]
480    async fn test_yew_dependency_detection() {
481        let provider = YewProvider::new();
482        let mut context = create_yew_context();
483        context.project_type = ProjectType::Binary; // Not explicitly Yew
484        // But has yew dependency (already set in create_yew_context)
485
486        assert!(provider.can_handle(&context));
487    }
488
489    #[tokio::test]
490    async fn test_yew_router_dependency_detection() {
491        let provider = YewProvider::new();
492        let mut context = create_yew_context();
493        context.project_type = ProjectType::Binary;
494        context.dependencies.clear();
495        context.dependencies.push(Dependency {
496            name: "yew-router".to_string(),
497            version: "0.18".to_string(),
498            features: vec![],
499            optional: false,
500            dev_dependency: false,
501        });
502
503        assert!(provider.can_handle(&context));
504    }
505}