1use crate::{
7 Command, CommandBuilder, CommandCategory, CommandProvider, ProjectContext, ProjectType,
8 RazResult, SymbolKind,
9};
10use async_trait::async_trait;
11
12pub struct YewProvider {
14 priority: u8,
15}
16
17impl YewProvider {
18 pub fn new() -> Self {
19 Self {
20 priority: 90, }
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 match &context.project_type {
38 ProjectType::Yew => true,
39 ProjectType::Mixed(frameworks) => frameworks.contains(&ProjectType::Yew),
40 _ => {
41 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 commands.extend(self.development_commands(context));
54
55 commands.extend(self.build_commands(context));
57
58 commands.extend(self.test_commands(context));
60
61 commands.extend(self.context_aware_commands(context));
63
64 commands.extend(self.deployment_commands(context));
66
67 Ok(commands)
68 }
69}
70
71impl YewProvider {
72 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 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 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 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 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 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 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 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 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 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 assert!(commands.iter().any(|c| c.id == "yew-serve"));
441 assert!(commands.iter().any(|c| c.id == "yew-serve-open"));
442
443 assert!(commands.iter().any(|c| c.id == "yew-build"));
445 assert!(commands.iter().any(|c| c.id == "yew-build-release"));
446
447 assert!(commands.iter().any(|c| c.id == "yew-test"));
449
450 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 let serve_cmd = commands.iter().find(|c| c.id == "yew-serve").unwrap();
463 assert_eq!(serve_cmd.priority, 95);
464
465 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; 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}