resolver.rs
1 use crate::analysis::graph::{BindingGraph, EnvVarLocationKind}; 2 use crate::types::{ 3 BindingKind, EnvBinding, EnvBindingUsage, EnvReference, ResolvedEnv, ScopeId, Symbol, SymbolId, 4 SymbolUsage, 5 }; 6 use compact_str::CompactString; 7 use tower_lsp::lsp_types::{Position, Range}; 8 use tracing::error; 9 10 #[derive(Debug, Clone)] 11 pub enum EnvHit<'a> { 12 DirectReference(&'a EnvReference), 13 14 ViaSymbol { 15 symbol: &'a Symbol, 16 resolved: ResolvedEnv, 17 }, 18 19 ViaUsage { 20 usage: &'a SymbolUsage, 21 symbol: &'a Symbol, 22 resolved: ResolvedEnv, 23 }, 24 } 25 26 impl<'a> EnvHit<'a> { 27 pub fn env_var_name(&self) -> Option<CompactString> { 28 match self { 29 EnvHit::DirectReference(r) => Some(r.name.clone()), 30 EnvHit::ViaSymbol { resolved, .. } | EnvHit::ViaUsage { resolved, .. } => { 31 match resolved { 32 ResolvedEnv::Variable(name) => Some(name.clone()), 33 ResolvedEnv::Object(_) => None, 34 } 35 } 36 } 37 } 38 39 pub fn canonical_name(&self) -> CompactString { 40 match self { 41 EnvHit::DirectReference(r) => r.name.clone(), 42 EnvHit::ViaSymbol { resolved, .. } | EnvHit::ViaUsage { resolved, .. } => { 43 match resolved { 44 ResolvedEnv::Variable(name) => name.clone(), 45 ResolvedEnv::Object(name) => name.clone(), 46 } 47 } 48 } 49 } 50 51 pub fn range(&self) -> Range { 52 match self { 53 EnvHit::DirectReference(r) => r.name_range, 54 EnvHit::ViaSymbol { symbol, .. } => symbol.name_range, 55 EnvHit::ViaUsage { usage, .. } => usage.range, 56 } 57 } 58 59 pub fn is_env_object(&self) -> bool { 60 match self { 61 EnvHit::DirectReference(_) => false, 62 EnvHit::ViaSymbol { resolved, .. } | EnvHit::ViaUsage { resolved, .. } => { 63 matches!(resolved, ResolvedEnv::Object(_)) 64 } 65 } 66 } 67 68 pub fn binding_name(&self) -> Option<&CompactString> { 69 match self { 70 EnvHit::DirectReference(_) => None, 71 EnvHit::ViaSymbol { symbol, .. } => Some(&symbol.name), 72 EnvHit::ViaUsage { symbol, .. } => Some(&symbol.name), 73 } 74 } 75 } 76 77 #[derive(Debug, Clone)] 78 pub struct ResolvedBinding { 79 pub binding_name: CompactString, 80 81 pub env_var_name: CompactString, 82 83 pub binding_range: Range, 84 85 pub declaration_range: Range, 86 87 pub kind: BindingKind, 88 89 pub is_usage: bool, 90 } 91 92 impl ResolvedBinding { 93 pub fn to_env_binding(&self, scope_range: Range) -> EnvBinding { 94 EnvBinding { 95 binding_name: self.binding_name.clone(), 96 env_var_name: self.env_var_name.clone(), 97 binding_range: self.binding_range, 98 declaration_range: self.declaration_range, 99 scope_range, 100 is_valid: true, 101 kind: self.kind.clone(), 102 destructured_key_range: None, 103 } 104 } 105 106 pub fn to_binding_usage(&self) -> EnvBindingUsage { 107 EnvBindingUsage { 108 name: self.binding_name.clone(), 109 range: self.binding_range, 110 declaration_range: self.declaration_range, 111 env_var_name: self.env_var_name.clone(), 112 } 113 } 114 } 115 116 pub struct BindingResolver<'a> { 117 graph: &'a BindingGraph, 118 } 119 120 impl<'a> BindingResolver<'a> { 121 pub fn new(graph: &'a BindingGraph) -> Self { 122 Self { graph } 123 } 124 125 pub fn env_at_position(&self, position: Position) -> Option<EnvHit<'a>> { 126 tracing::trace!( 127 "Looking up env at position line={}, char={}", 128 position.line, 129 position.character 130 ); 131 132 for reference in self.graph.direct_references() { 133 if BindingGraph::contains_position(reference.name_range, position) { 134 return Some(EnvHit::DirectReference(reference)); 135 } 136 } 137 138 if let Some(symbol) = self.graph.symbol_at_position(position) { 139 if let Some(resolved) = self.graph.resolve_to_env(symbol.id) { 140 return Some(EnvHit::ViaSymbol { symbol, resolved }); 141 } 142 } 143 144 if let Some(symbol_id) = self.graph.symbol_at_destructure_key(position) { 145 if let Some(symbol) = self.graph.get_symbol(symbol_id) { 146 if let Some(resolved) = self.graph.resolve_to_env(symbol_id) { 147 return Some(EnvHit::ViaSymbol { symbol, resolved }); 148 } 149 } 150 } 151 152 if let Some(usage) = self.graph.usage_at_position(position) { 153 if let Some(symbol) = self.graph.get_symbol(usage.symbol_id) { 154 if let Some(property) = &usage.property_access { 155 if let Some(resolved) = self.graph.resolve_to_env(usage.symbol_id) { 156 if matches!(resolved, ResolvedEnv::Object(_)) { 157 return Some(EnvHit::ViaUsage { 158 usage, 159 symbol, 160 resolved: ResolvedEnv::Variable(property.clone()), 161 }); 162 } 163 } 164 } else if let Some(resolved) = self.graph.resolve_to_env(usage.symbol_id) { 165 return Some(EnvHit::ViaUsage { 166 usage, 167 symbol, 168 resolved, 169 }); 170 } 171 } 172 } 173 174 tracing::debug!( 175 "No env var found at position line={}, char={}", 176 position.line, 177 position.character 178 ); 179 None 180 } 181 182 pub fn binding_at_position(&self, position: Position) -> Option<ResolvedBinding> { 183 let hit = self.env_at_position(position)?; 184 185 match hit { 186 EnvHit::DirectReference(_) => None, 187 188 EnvHit::ViaSymbol { symbol, resolved } => { 189 let env_var_name = match &resolved { 190 ResolvedEnv::Variable(name) => name.clone(), 191 ResolvedEnv::Object(name) => name.clone(), 192 }; 193 194 let kind = match &resolved { 195 ResolvedEnv::Variable(_) => BindingKind::Value, 196 ResolvedEnv::Object(_) => BindingKind::Object, 197 }; 198 199 Some(ResolvedBinding { 200 binding_name: symbol.name.clone(), 201 env_var_name, 202 binding_range: symbol.name_range, 203 declaration_range: symbol.declaration_range, 204 kind, 205 is_usage: false, 206 }) 207 } 208 209 EnvHit::ViaUsage { 210 usage, 211 symbol, 212 resolved, 213 } => { 214 let env_var_name = match &resolved { 215 ResolvedEnv::Variable(name) => name.clone(), 216 ResolvedEnv::Object(name) => name.clone(), 217 }; 218 219 let kind = match &resolved { 220 ResolvedEnv::Variable(_) => BindingKind::Value, 221 ResolvedEnv::Object(_) => BindingKind::Object, 222 }; 223 224 Some(ResolvedBinding { 225 binding_name: symbol.name.clone(), 226 env_var_name, 227 binding_range: usage.range, 228 declaration_range: symbol.declaration_range, 229 kind, 230 is_usage: true, 231 }) 232 } 233 } 234 } 235 236 pub fn direct_reference_at_position(&self, position: Position) -> Option<&'a EnvReference> { 237 self.graph 238 .direct_references() 239 .iter() 240 .find(|&reference| BindingGraph::contains_position(reference.name_range, position)) 241 } 242 243 /// Find all usages of a given env var name. 244 /// Uses O(1) index lookup if available, falls back to O(n) scan otherwise. 245 pub fn find_env_var_usages(&self, env_var_name: &str) -> Vec<EnvVarUsageLocation> { 246 // Try the pre-built index first for O(1) lookup 247 if let Some(locations) = self.graph.get_env_var_locations(env_var_name) { 248 return locations 249 .iter() 250 .map(|loc| EnvVarUsageLocation { 251 range: loc.range, 252 kind: Self::convert_location_kind(loc.kind), 253 binding_name: loc.binding_name.clone(), 254 }) 255 .collect(); 256 } 257 258 // Fall back to scanning (for when rebuild_range_index hasn't been called) 259 self.find_env_var_usages_scan(env_var_name) 260 } 261 262 /// Scan-based fallback for finding env var usages (O(n)) 263 fn find_env_var_usages_scan(&self, env_var_name: &str) -> Vec<EnvVarUsageLocation> { 264 let mut locations = Vec::new(); 265 let mut seen_ranges = std::collections::HashSet::new(); 266 267 for reference in self.graph.direct_references() { 268 if reference.name == env_var_name { 269 let range_key = ( 270 reference.name_range.start.line, 271 reference.name_range.start.character, 272 reference.name_range.end.line, 273 reference.name_range.end.character, 274 ); 275 if seen_ranges.insert(range_key) { 276 locations.push(EnvVarUsageLocation { 277 range: reference.name_range, 278 kind: UsageKind::DirectReference, 279 binding_name: None, 280 }); 281 } 282 } 283 } 284 285 for symbol in self.graph.symbols() { 286 if let Some(ResolvedEnv::Variable(name)) = self.graph.resolve_to_env(symbol.id) { 287 if name == env_var_name { 288 let rename_range = if let Some(key_range) = symbol.destructured_key_range { 289 Some(key_range) 290 } else if symbol.name.as_str() == env_var_name { 291 Some(symbol.name_range) 292 } else { 293 None 294 }; 295 296 if let Some(range) = rename_range { 297 let range_key = ( 298 range.start.line, 299 range.start.character, 300 range.end.line, 301 range.end.character, 302 ); 303 if seen_ranges.insert(range_key) { 304 locations.push(EnvVarUsageLocation { 305 range, 306 kind: UsageKind::BindingDeclaration, 307 binding_name: Some(symbol.name.clone()), 308 }); 309 } 310 } 311 } 312 } 313 } 314 315 for usage in self.graph.usages() { 316 if let Some(resolved) = self.graph.resolve_to_env(usage.symbol_id) { 317 match &resolved { 318 ResolvedEnv::Variable(name) if name == env_var_name => { 319 let range_key = ( 320 usage.range.start.line, 321 usage.range.start.character, 322 usage.range.end.line, 323 usage.range.end.character, 324 ); 325 if seen_ranges.insert(range_key) { 326 let binding_name = self 327 .graph 328 .get_symbol(usage.symbol_id) 329 .map(|s| s.name.clone()); 330 locations.push(EnvVarUsageLocation { 331 range: usage.range, 332 kind: UsageKind::BindingUsage, 333 binding_name, 334 }); 335 } 336 } 337 ResolvedEnv::Object(_) => { 338 if let Some(prop) = &usage.property_access { 339 if prop == env_var_name { 340 let range = usage.property_access_range.unwrap_or(usage.range); 341 342 let range_key = ( 343 range.start.line, 344 range.start.character, 345 range.end.line, 346 range.end.character, 347 ); 348 if seen_ranges.insert(range_key) { 349 let binding_name = self 350 .graph 351 .get_symbol(usage.symbol_id) 352 .map(|s| s.name.clone()); 353 locations.push(EnvVarUsageLocation { 354 range, 355 kind: UsageKind::PropertyAccess, 356 binding_name, 357 }); 358 } 359 } 360 } 361 } 362 _ => {} 363 } 364 } 365 } 366 367 locations 368 } 369 370 /// Convert from binding_graph's EnvVarLocationKind to resolver's UsageKind 371 fn convert_location_kind(kind: EnvVarLocationKind) -> UsageKind { 372 match kind { 373 EnvVarLocationKind::DirectReference => UsageKind::DirectReference, 374 EnvVarLocationKind::BindingDeclaration => UsageKind::BindingDeclaration, 375 EnvVarLocationKind::BindingUsage => UsageKind::BindingUsage, 376 EnvVarLocationKind::PropertyAccess => UsageKind::PropertyAccess, 377 } 378 } 379 380 pub fn all_env_vars(&self) -> Vec<CompactString> { 381 let mut vars = std::collections::HashSet::new(); 382 383 for reference in self.graph.direct_references() { 384 vars.insert(reference.name.clone()); 385 } 386 387 for symbol in self.graph.symbols() { 388 if let Some(ResolvedEnv::Variable(name)) = self.graph.resolve_to_env(symbol.id) { 389 vars.insert(name); 390 } 391 } 392 393 vars.into_iter().collect() 394 } 395 396 pub fn get_symbol(&self, id: SymbolId) -> Option<&'a Symbol> { 397 self.graph.get_symbol(id) 398 } 399 400 pub fn lookup_symbol(&self, name: &str, scope: ScopeId) -> Option<&'a Symbol> { 401 self.graph.lookup_symbol(name, scope) 402 } 403 404 pub fn scope_at_position(&self, position: Position) -> ScopeId { 405 self.graph.scope_at_position(position) 406 } 407 408 pub fn is_env_object(&self, symbol_id: SymbolId) -> bool { 409 self.graph.resolves_to_env_object(symbol_id) 410 } 411 412 pub fn get_env_reference_cloned(&self, position: Position) -> Option<EnvReference> { 413 if let Some(reference) = self.direct_reference_at_position(position) { 414 return Some(reference.clone()); 415 } 416 417 if let Some(usage) = self.graph.usage_at_position(position) { 418 if let Some(property) = &usage.property_access { 419 if let Some(resolved) = self.graph.resolve_to_env(usage.symbol_id) { 420 if matches!(resolved, ResolvedEnv::Object(_)) { 421 return Some(EnvReference { 422 name: property.clone(), 423 full_range: usage.range, 424 name_range: usage.range, 425 access_type: crate::types::AccessType::Property, 426 has_default: false, 427 default_value: None, 428 }); 429 } 430 } 431 } 432 } 433 434 None 435 } 436 437 pub fn get_env_binding_cloned(&self, position: Position) -> Option<EnvBinding> { 438 let hit = self.env_at_position(position)?; 439 440 match hit { 441 EnvHit::DirectReference(_) => None, 442 EnvHit::ViaUsage { .. } => None, 443 444 EnvHit::ViaSymbol { symbol, resolved } => { 445 let env_var_name = match &resolved { 446 ResolvedEnv::Variable(name) => name.clone(), 447 ResolvedEnv::Object(name) => name.clone(), 448 }; 449 450 let kind = match &resolved { 451 ResolvedEnv::Variable(_) => BindingKind::Value, 452 ResolvedEnv::Object(_) => BindingKind::Object, 453 }; 454 455 let scope = match self.graph.get_scope(symbol.scope) { 456 Some(s) => s, 457 None => { 458 // Data consistency error - this should never happen in normal operation 459 error!( 460 symbol_name = %symbol.name, 461 scope_id = ?symbol.scope, 462 "Symbol references non-existent scope - data consistency error" 463 ); 464 return None; 465 } 466 }; 467 468 Some(EnvBinding { 469 binding_name: symbol.name.clone(), 470 env_var_name, 471 binding_range: symbol.name_range, 472 declaration_range: symbol.declaration_range, 473 scope_range: scope.range, 474 is_valid: symbol.is_valid, 475 kind, 476 destructured_key_range: symbol.destructured_key_range, 477 }) 478 } 479 } 480 } 481 482 pub fn get_binding_usage_cloned(&self, position: Position) -> Option<EnvBindingUsage> { 483 let binding = self.binding_at_position(position)?; 484 if !binding.is_usage { 485 return None; 486 } 487 Some(binding.to_binding_usage()) 488 } 489 490 pub fn get_binding_kind(&self, name: &str) -> Option<BindingKind> { 491 for symbol in self.graph.symbols() { 492 if symbol.name == name && symbol.is_valid { 493 if let Some(resolved) = self.graph.resolve_to_env(symbol.id) { 494 return Some(match resolved { 495 ResolvedEnv::Variable(_) => BindingKind::Value, 496 ResolvedEnv::Object(_) => BindingKind::Object, 497 }); 498 } 499 } 500 } 501 None 502 } 503 } 504 505 #[derive(Debug, Clone)] 506 pub struct EnvVarUsageLocation { 507 pub range: Range, 508 509 pub kind: UsageKind, 510 511 pub binding_name: Option<CompactString>, 512 } 513 514 #[derive(Debug, Clone, Copy, PartialEq, Eq)] 515 pub enum UsageKind { 516 DirectReference, 517 518 BindingDeclaration, 519 520 BindingUsage, 521 522 PropertyAccess, 523 } 524 525 #[cfg(test)] 526 mod tests { 527 use super::*; 528 use crate::types::{AccessType, ScopeKind, SymbolKind, SymbolOrigin}; 529 530 fn make_range(start_line: u32, start_char: u32, end_line: u32, end_char: u32) -> Range { 531 Range::new( 532 Position::new(start_line, start_char), 533 Position::new(end_line, end_char), 534 ) 535 } 536 537 #[test] 538 fn test_env_hit_direct_reference_methods() { 539 let mut graph = BindingGraph::new(); 540 541 let reference = EnvReference { 542 name: "DATABASE_URL".into(), 543 full_range: make_range(0, 0, 0, 30), 544 name_range: make_range(0, 12, 0, 24), 545 access_type: AccessType::Property, 546 has_default: false, 547 default_value: None, 548 }; 549 graph.add_direct_reference(reference); 550 551 let resolver = BindingResolver::new(&graph); 552 let hit = resolver.env_at_position(Position::new(0, 15)).unwrap(); 553 554 assert_eq!(hit.env_var_name(), Some("DATABASE_URL".into())); 555 assert_eq!(hit.canonical_name().as_str(), "DATABASE_URL"); 556 assert_eq!(hit.range(), make_range(0, 12, 0, 24)); 557 assert!(!hit.is_env_object()); 558 assert!(hit.binding_name().is_none()); 559 } 560 561 #[test] 562 fn test_env_hit_via_symbol_env_var() { 563 let mut graph = BindingGraph::new(); 564 565 let _id = graph.add_symbol(Symbol { 566 id: SymbolId::new(1).unwrap(), 567 name: "dbUrl".into(), 568 declaration_range: make_range(0, 0, 0, 40), 569 name_range: make_range(0, 6, 0, 11), 570 scope: ScopeId::root(), 571 origin: SymbolOrigin::EnvVar { 572 name: "DATABASE_URL".into(), 573 }, 574 kind: SymbolKind::Value, 575 is_valid: true, 576 destructured_key_range: None, 577 }); 578 graph.rebuild_range_index(); 579 580 let resolver = BindingResolver::new(&graph); 581 let hit = resolver.env_at_position(Position::new(0, 8)).unwrap(); 582 583 assert_eq!(hit.env_var_name(), Some("DATABASE_URL".into())); 584 assert_eq!(hit.canonical_name().as_str(), "DATABASE_URL"); 585 assert_eq!(hit.range(), make_range(0, 6, 0, 11)); 586 assert!(!hit.is_env_object()); 587 assert_eq!(hit.binding_name(), Some(&"dbUrl".into())); 588 } 589 590 #[test] 591 fn test_env_hit_via_symbol_env_object() { 592 let mut graph = BindingGraph::new(); 593 594 let _id = graph.add_symbol(Symbol { 595 id: SymbolId::new(1).unwrap(), 596 name: "env".into(), 597 declaration_range: make_range(0, 0, 0, 25), 598 name_range: make_range(0, 6, 0, 9), 599 scope: ScopeId::root(), 600 origin: SymbolOrigin::EnvObject { 601 canonical_name: "process.env".into(), 602 }, 603 kind: SymbolKind::EnvObject, 604 is_valid: true, 605 destructured_key_range: None, 606 }); 607 graph.rebuild_range_index(); 608 609 let resolver = BindingResolver::new(&graph); 610 let hit = resolver.env_at_position(Position::new(0, 7)).unwrap(); 611 612 assert!(hit.env_var_name().is_none()); 613 assert_eq!(hit.canonical_name().as_str(), "process.env"); 614 assert!(hit.is_env_object()); 615 assert_eq!(hit.binding_name(), Some(&"env".into())); 616 } 617 618 #[test] 619 fn test_env_hit_via_usage() { 620 let mut graph = BindingGraph::new(); 621 622 let id = graph.add_symbol(Symbol { 623 id: SymbolId::new(1).unwrap(), 624 name: "apiKey".into(), 625 declaration_range: make_range(0, 0, 0, 35), 626 name_range: make_range(0, 6, 0, 12), 627 scope: ScopeId::root(), 628 origin: SymbolOrigin::EnvVar { 629 name: "API_KEY".into(), 630 }, 631 kind: SymbolKind::Value, 632 is_valid: true, 633 destructured_key_range: None, 634 }); 635 636 graph.add_usage(SymbolUsage { 637 symbol_id: id, 638 range: make_range(2, 10, 2, 16), 639 scope: ScopeId::root(), 640 property_access: None, 641 property_access_range: None, 642 }); 643 graph.rebuild_range_index(); 644 645 let resolver = BindingResolver::new(&graph); 646 let hit = resolver.env_at_position(Position::new(2, 12)).unwrap(); 647 648 assert!(matches!(hit, EnvHit::ViaUsage { .. })); 649 assert_eq!(hit.env_var_name(), Some("API_KEY".into())); 650 assert_eq!(hit.canonical_name().as_str(), "API_KEY"); 651 assert_eq!(hit.range(), make_range(2, 10, 2, 16)); 652 assert!(!hit.is_env_object()); 653 assert_eq!(hit.binding_name(), Some(&"apiKey".into())); 654 } 655 656 #[test] 657 fn test_env_hit_via_usage_property_access() { 658 let mut graph = BindingGraph::new(); 659 660 let id = graph.add_symbol(Symbol { 661 id: SymbolId::new(1).unwrap(), 662 name: "env".into(), 663 declaration_range: make_range(0, 0, 0, 25), 664 name_range: make_range(0, 6, 0, 9), 665 scope: ScopeId::root(), 666 origin: SymbolOrigin::EnvObject { 667 canonical_name: "process.env".into(), 668 }, 669 kind: SymbolKind::EnvObject, 670 is_valid: true, 671 destructured_key_range: None, 672 }); 673 674 graph.add_usage(SymbolUsage { 675 symbol_id: id, 676 range: make_range(1, 0, 1, 16), 677 scope: ScopeId::root(), 678 property_access: Some("DATABASE_URL".into()), 679 property_access_range: Some(make_range(1, 4, 1, 16)), 680 }); 681 graph.rebuild_range_index(); 682 683 let resolver = BindingResolver::new(&graph); 684 let hit = resolver.env_at_position(Position::new(1, 8)).unwrap(); 685 686 assert!(matches!(hit, EnvHit::ViaUsage { .. })); 687 688 assert_eq!(hit.env_var_name(), Some("DATABASE_URL".into())); 689 assert!(!hit.is_env_object()); 690 } 691 692 #[test] 693 fn test_resolved_binding_to_env_binding() { 694 let binding = ResolvedBinding { 695 binding_name: "dbUrl".into(), 696 env_var_name: "DATABASE_URL".into(), 697 binding_range: make_range(0, 6, 0, 11), 698 declaration_range: make_range(0, 0, 0, 40), 699 kind: BindingKind::Value, 700 is_usage: false, 701 }; 702 703 let scope_range = make_range(0, 0, 10, 0); 704 let env_binding = binding.to_env_binding(scope_range); 705 706 assert_eq!(env_binding.binding_name, "dbUrl"); 707 assert_eq!(env_binding.env_var_name, "DATABASE_URL"); 708 assert_eq!(env_binding.binding_range, make_range(0, 6, 0, 11)); 709 assert_eq!(env_binding.declaration_range, make_range(0, 0, 0, 40)); 710 assert_eq!(env_binding.scope_range, scope_range); 711 assert!(env_binding.is_valid); 712 assert_eq!(env_binding.kind, BindingKind::Value); 713 assert!(env_binding.destructured_key_range.is_none()); 714 } 715 716 #[test] 717 fn test_resolved_binding_to_binding_usage() { 718 let binding = ResolvedBinding { 719 binding_name: "apiKey".into(), 720 env_var_name: "API_KEY".into(), 721 binding_range: make_range(2, 10, 2, 16), 722 declaration_range: make_range(0, 0, 0, 35), 723 kind: BindingKind::Value, 724 is_usage: true, 725 }; 726 727 let usage = binding.to_binding_usage(); 728 729 assert_eq!(usage.name, "apiKey"); 730 assert_eq!(usage.env_var_name, "API_KEY"); 731 assert_eq!(usage.range, make_range(2, 10, 2, 16)); 732 assert_eq!(usage.declaration_range, make_range(0, 0, 0, 35)); 733 } 734 735 #[test] 736 fn test_resolved_binding_object_kind() { 737 let binding = ResolvedBinding { 738 binding_name: "env".into(), 739 env_var_name: "process.env".into(), 740 binding_range: make_range(0, 6, 0, 9), 741 declaration_range: make_range(0, 0, 0, 25), 742 kind: BindingKind::Object, 743 is_usage: false, 744 }; 745 746 let env_binding = binding.to_env_binding(make_range(0, 0, 100, 0)); 747 assert_eq!(env_binding.kind, BindingKind::Object); 748 } 749 750 #[test] 751 fn test_resolve_direct_reference() { 752 let mut graph = BindingGraph::new(); 753 754 graph.add_direct_reference(EnvReference { 755 name: "DATABASE_URL".into(), 756 full_range: make_range(0, 0, 0, 30), 757 name_range: make_range(0, 12, 0, 24), 758 access_type: crate::types::AccessType::Property, 759 has_default: false, 760 default_value: None, 761 }); 762 763 let resolver = BindingResolver::new(&graph); 764 765 let hit = resolver.env_at_position(Position::new(0, 15)).unwrap(); 766 assert!(matches!(hit, EnvHit::DirectReference(_))); 767 assert_eq!(hit.env_var_name(), Some("DATABASE_URL".into())); 768 } 769 770 #[test] 771 fn test_resolve_via_symbol() { 772 let mut graph = BindingGraph::new(); 773 774 let _id = graph.add_symbol(Symbol { 775 id: SymbolId::new(1).unwrap(), 776 name: "dbUrl".into(), 777 declaration_range: make_range(0, 0, 0, 40), 778 name_range: make_range(0, 6, 0, 11), 779 scope: ScopeId::root(), 780 origin: SymbolOrigin::EnvVar { 781 name: "DATABASE_URL".into(), 782 }, 783 kind: SymbolKind::Value, 784 is_valid: true, 785 destructured_key_range: None, 786 }); 787 graph.rebuild_range_index(); 788 789 let resolver = BindingResolver::new(&graph); 790 791 let hit = resolver.env_at_position(Position::new(0, 8)).unwrap(); 792 assert!(matches!(hit, EnvHit::ViaSymbol { .. })); 793 assert_eq!(hit.env_var_name(), Some("DATABASE_URL".into())); 794 assert_eq!(hit.binding_name(), Some(&"dbUrl".into())); 795 } 796 797 #[test] 798 fn test_resolve_chain() { 799 let mut graph = BindingGraph::new(); 800 801 let env_id = graph.add_symbol(Symbol { 802 id: SymbolId::new(1).unwrap(), 803 name: "env".into(), 804 declaration_range: make_range(0, 0, 0, 25), 805 name_range: make_range(0, 6, 0, 9), 806 scope: ScopeId::root(), 807 origin: SymbolOrigin::EnvObject { 808 canonical_name: "process.env".into(), 809 }, 810 kind: SymbolKind::EnvObject, 811 is_valid: true, 812 destructured_key_range: None, 813 }); 814 815 let config_id = graph.add_symbol(Symbol { 816 id: SymbolId::new(2).unwrap(), 817 name: "config".into(), 818 declaration_range: make_range(1, 0, 1, 20), 819 name_range: make_range(1, 6, 1, 12), 820 scope: ScopeId::root(), 821 origin: SymbolOrigin::Symbol { target: env_id }, 822 kind: SymbolKind::Variable, 823 is_valid: true, 824 destructured_key_range: None, 825 }); 826 827 let _db_url_id = graph.add_symbol(Symbol { 828 id: SymbolId::new(3).unwrap(), 829 name: "DB_URL".into(), 830 declaration_range: make_range(2, 0, 2, 30), 831 name_range: make_range(2, 8, 2, 14), 832 scope: ScopeId::root(), 833 origin: SymbolOrigin::DestructuredProperty { 834 source: config_id, 835 key: "DB_URL".into(), 836 }, 837 kind: SymbolKind::DestructuredProperty, 838 is_valid: true, 839 destructured_key_range: Some(make_range(2, 8, 2, 14)), 840 }); 841 graph.rebuild_range_index(); 842 843 let resolver = BindingResolver::new(&graph); 844 845 let hit = resolver.env_at_position(Position::new(2, 10)).unwrap(); 846 assert!(matches!(hit, EnvHit::ViaSymbol { .. })); 847 assert_eq!(hit.env_var_name(), Some("DB_URL".into())); 848 } 849 850 #[test] 851 fn test_binding_at_position_symbol() { 852 let mut graph = BindingGraph::new(); 853 854 let _id = graph.add_symbol(Symbol { 855 id: SymbolId::new(1).unwrap(), 856 name: "dbUrl".into(), 857 declaration_range: make_range(0, 0, 0, 40), 858 name_range: make_range(0, 6, 0, 11), 859 scope: ScopeId::root(), 860 origin: SymbolOrigin::EnvVar { 861 name: "DATABASE_URL".into(), 862 }, 863 kind: SymbolKind::Value, 864 is_valid: true, 865 destructured_key_range: None, 866 }); 867 graph.rebuild_range_index(); 868 869 let resolver = BindingResolver::new(&graph); 870 let binding = resolver.binding_at_position(Position::new(0, 8)).unwrap(); 871 872 assert_eq!(binding.binding_name, "dbUrl"); 873 assert_eq!(binding.env_var_name, "DATABASE_URL"); 874 assert!(!binding.is_usage); 875 assert_eq!(binding.kind, BindingKind::Value); 876 } 877 878 #[test] 879 fn test_binding_at_position_usage() { 880 let mut graph = BindingGraph::new(); 881 882 let id = graph.add_symbol(Symbol { 883 id: SymbolId::new(1).unwrap(), 884 name: "apiKey".into(), 885 declaration_range: make_range(0, 0, 0, 35), 886 name_range: make_range(0, 6, 0, 12), 887 scope: ScopeId::root(), 888 origin: SymbolOrigin::EnvVar { 889 name: "API_KEY".into(), 890 }, 891 kind: SymbolKind::Value, 892 is_valid: true, 893 destructured_key_range: None, 894 }); 895 896 graph.add_usage(SymbolUsage { 897 symbol_id: id, 898 range: make_range(2, 10, 2, 16), 899 scope: ScopeId::root(), 900 property_access: None, 901 property_access_range: None, 902 }); 903 graph.rebuild_range_index(); 904 905 let resolver = BindingResolver::new(&graph); 906 let binding = resolver.binding_at_position(Position::new(2, 12)).unwrap(); 907 908 assert_eq!(binding.binding_name, "apiKey"); 909 assert_eq!(binding.env_var_name, "API_KEY"); 910 assert!(binding.is_usage); 911 } 912 913 #[test] 914 fn test_binding_at_position_direct_reference_returns_none() { 915 let mut graph = BindingGraph::new(); 916 917 graph.add_direct_reference(EnvReference { 918 name: "DATABASE_URL".into(), 919 full_range: make_range(0, 0, 0, 30), 920 name_range: make_range(0, 12, 0, 24), 921 access_type: AccessType::Property, 922 has_default: false, 923 default_value: None, 924 }); 925 926 let resolver = BindingResolver::new(&graph); 927 928 assert!(resolver.binding_at_position(Position::new(0, 15)).is_none()); 929 } 930 931 #[test] 932 fn test_direct_reference_at_position() { 933 let mut graph = BindingGraph::new(); 934 935 graph.add_direct_reference(EnvReference { 936 name: "API_KEY".into(), 937 full_range: make_range(0, 0, 0, 25), 938 name_range: make_range(0, 12, 0, 19), 939 access_type: AccessType::Property, 940 has_default: false, 941 default_value: None, 942 }); 943 944 let resolver = BindingResolver::new(&graph); 945 946 let ref_at_full_only = resolver.direct_reference_at_position(Position::new(0, 5)); 947 assert!( 948 ref_at_full_only.is_none(), 949 "Position in full_range but outside name_range should return None" 950 ); 951 952 let ref_at_name = resolver.direct_reference_at_position(Position::new(0, 15)); 953 assert!(ref_at_name.is_some()); 954 assert_eq!(ref_at_name.unwrap().name, "API_KEY"); 955 956 let ref_outside = resolver.direct_reference_at_position(Position::new(1, 0)); 957 assert!(ref_outside.is_none()); 958 } 959 960 #[test] 961 fn test_env_at_position_outside_any_env_returns_none() { 962 let graph = BindingGraph::new(); 963 let resolver = BindingResolver::new(&graph); 964 965 assert!(resolver.env_at_position(Position::new(0, 0)).is_none()); 966 assert!(resolver.env_at_position(Position::new(100, 100)).is_none()); 967 } 968 969 #[test] 970 fn test_env_at_position_with_destructure_key() { 971 let mut graph = BindingGraph::new(); 972 973 let _id = graph.add_symbol(Symbol { 974 id: SymbolId::new(1).unwrap(), 975 name: "apiKey".into(), 976 declaration_range: make_range(0, 0, 0, 40), 977 name_range: make_range(0, 18, 0, 24), 978 scope: ScopeId::root(), 979 origin: SymbolOrigin::EnvVar { 980 name: "API_KEY".into(), 981 }, 982 kind: SymbolKind::DestructuredProperty, 983 is_valid: true, 984 destructured_key_range: Some(make_range(0, 8, 0, 15)), 985 }); 986 graph.rebuild_range_index(); 987 988 let resolver = BindingResolver::new(&graph); 989 990 let hit = resolver.env_at_position(Position::new(0, 10)); 991 assert!(hit.is_some()); 992 let hit = hit.unwrap(); 993 assert_eq!(hit.env_var_name(), Some("API_KEY".into())); 994 995 let hit2 = resolver.env_at_position(Position::new(0, 20)); 996 assert!(hit2.is_some()); 997 } 998 999 #[test] 1000 fn test_find_usages() { 1001 let mut graph = BindingGraph::new(); 1002 1003 graph.add_direct_reference(EnvReference { 1004 name: "API_KEY".into(), 1005 full_range: make_range(0, 0, 0, 20), 1006 name_range: make_range(0, 12, 0, 19), 1007 access_type: crate::types::AccessType::Property, 1008 has_default: false, 1009 default_value: None, 1010 }); 1011 1012 let id = graph.add_symbol(Symbol { 1013 id: SymbolId::new(1).unwrap(), 1014 name: "apiKey".into(), 1015 declaration_range: make_range(1, 0, 1, 35), 1016 name_range: make_range(1, 6, 1, 12), 1017 scope: ScopeId::root(), 1018 origin: SymbolOrigin::EnvVar { 1019 name: "API_KEY".into(), 1020 }, 1021 kind: SymbolKind::Value, 1022 is_valid: true, 1023 destructured_key_range: None, 1024 }); 1025 1026 graph.add_usage(SymbolUsage { 1027 symbol_id: id, 1028 range: make_range(2, 10, 2, 16), 1029 scope: ScopeId::root(), 1030 property_access: None, 1031 property_access_range: None, 1032 }); 1033 1034 let resolver = BindingResolver::new(&graph); 1035 1036 let usages = resolver.find_env_var_usages("API_KEY"); 1037 1038 assert_eq!(usages.len(), 2); 1039 1040 assert!(usages.iter().any(|u| u.kind == UsageKind::DirectReference)); 1041 assert!(usages.iter().any(|u| u.kind == UsageKind::BindingUsage)); 1042 } 1043 1044 #[test] 1045 fn test_find_usages_destructured() { 1046 let mut graph = BindingGraph::new(); 1047 1048 graph.add_symbol(Symbol { 1049 id: SymbolId::new(1).unwrap(), 1050 name: "API_KEY".into(), 1051 declaration_range: make_range(0, 0, 0, 35), 1052 name_range: make_range(0, 8, 0, 15), 1053 scope: ScopeId::root(), 1054 origin: SymbolOrigin::EnvVar { 1055 name: "API_KEY".into(), 1056 }, 1057 kind: SymbolKind::DestructuredProperty, 1058 is_valid: true, 1059 destructured_key_range: None, 1060 }); 1061 1062 let resolver = BindingResolver::new(&graph); 1063 1064 let usages = resolver.find_env_var_usages("API_KEY"); 1065 1066 assert_eq!(usages.len(), 1); 1067 assert!(usages 1068 .iter() 1069 .any(|u| u.kind == UsageKind::BindingDeclaration)); 1070 } 1071 1072 #[test] 1073 fn test_find_usages_destructured_with_rename() { 1074 let mut graph = BindingGraph::new(); 1075 1076 graph.add_symbol(Symbol { 1077 id: SymbolId::new(1).unwrap(), 1078 name: "apiKey".into(), 1079 declaration_range: make_range(0, 0, 0, 45), 1080 name_range: make_range(0, 18, 0, 24), 1081 scope: ScopeId::root(), 1082 origin: SymbolOrigin::EnvVar { 1083 name: "API_KEY".into(), 1084 }, 1085 kind: SymbolKind::DestructuredProperty, 1086 is_valid: true, 1087 destructured_key_range: Some(make_range(0, 8, 0, 15)), 1088 }); 1089 1090 let resolver = BindingResolver::new(&graph); 1091 1092 let usages = resolver.find_env_var_usages("API_KEY"); 1093 1094 assert_eq!(usages.len(), 1); 1095 assert!(usages 1096 .iter() 1097 .any(|u| u.kind == UsageKind::BindingDeclaration)); 1098 1099 assert_eq!(usages[0].range, make_range(0, 8, 0, 15)); 1100 } 1101 1102 #[test] 1103 fn test_find_usages_property_access() { 1104 let mut graph = BindingGraph::new(); 1105 1106 let id = graph.add_symbol(Symbol { 1107 id: SymbolId::new(1).unwrap(), 1108 name: "env".into(), 1109 declaration_range: make_range(0, 0, 0, 25), 1110 name_range: make_range(0, 6, 0, 9), 1111 scope: ScopeId::root(), 1112 origin: SymbolOrigin::EnvObject { 1113 canonical_name: "process.env".into(), 1114 }, 1115 kind: SymbolKind::EnvObject, 1116 is_valid: true, 1117 destructured_key_range: None, 1118 }); 1119 1120 graph.add_usage(SymbolUsage { 1121 symbol_id: id, 1122 range: make_range(1, 0, 1, 16), 1123 scope: ScopeId::root(), 1124 property_access: Some("DATABASE_URL".into()), 1125 property_access_range: Some(make_range(1, 4, 1, 16)), 1126 }); 1127 1128 let resolver = BindingResolver::new(&graph); 1129 let usages = resolver.find_env_var_usages("DATABASE_URL"); 1130 1131 assert_eq!(usages.len(), 1); 1132 assert!(usages.iter().any(|u| u.kind == UsageKind::PropertyAccess)); 1133 1134 assert_eq!(usages[0].range, make_range(1, 4, 1, 16)); 1135 } 1136 1137 #[test] 1138 fn test_find_usages_property_access_no_range() { 1139 let mut graph = BindingGraph::new(); 1140 1141 let id = graph.add_symbol(Symbol { 1142 id: SymbolId::new(1).unwrap(), 1143 name: "env".into(), 1144 declaration_range: make_range(0, 0, 0, 25), 1145 name_range: make_range(0, 6, 0, 9), 1146 scope: ScopeId::root(), 1147 origin: SymbolOrigin::EnvObject { 1148 canonical_name: "process.env".into(), 1149 }, 1150 kind: SymbolKind::EnvObject, 1151 is_valid: true, 1152 destructured_key_range: None, 1153 }); 1154 1155 graph.add_usage(SymbolUsage { 1156 symbol_id: id, 1157 range: make_range(2, 5, 2, 16), 1158 scope: ScopeId::root(), 1159 property_access: Some("API_KEY".into()), 1160 property_access_range: None, 1161 }); 1162 1163 let resolver = BindingResolver::new(&graph); 1164 let usages = resolver.find_env_var_usages("API_KEY"); 1165 1166 assert_eq!(usages.len(), 1); 1167 1168 assert_eq!(usages[0].range, make_range(2, 5, 2, 16)); 1169 } 1170 1171 #[test] 1172 fn test_find_usages_deduplication() { 1173 let mut graph = BindingGraph::new(); 1174 1175 graph.add_direct_reference(EnvReference { 1176 name: "DUPLICATE".into(), 1177 full_range: make_range(0, 0, 0, 20), 1178 name_range: make_range(0, 12, 0, 20), 1179 access_type: AccessType::Property, 1180 has_default: false, 1181 default_value: None, 1182 }); 1183 graph.add_direct_reference(EnvReference { 1184 name: "DUPLICATE".into(), 1185 full_range: make_range(0, 0, 0, 20), 1186 name_range: make_range(0, 12, 0, 20), 1187 access_type: AccessType::Property, 1188 has_default: false, 1189 default_value: None, 1190 }); 1191 1192 let resolver = BindingResolver::new(&graph); 1193 let usages = resolver.find_env_var_usages("DUPLICATE"); 1194 1195 assert_eq!(usages.len(), 1); 1196 } 1197 1198 #[test] 1199 fn test_find_usages_nonexistent_env_var() { 1200 let graph = BindingGraph::new(); 1201 let resolver = BindingResolver::new(&graph); 1202 1203 let usages = resolver.find_env_var_usages("NONEXISTENT"); 1204 assert!(usages.is_empty()); 1205 } 1206 1207 #[test] 1208 fn test_all_env_vars() { 1209 let mut graph = BindingGraph::new(); 1210 1211 graph.add_direct_reference(EnvReference { 1212 name: "VAR_ONE".into(), 1213 full_range: make_range(0, 0, 0, 20), 1214 name_range: make_range(0, 12, 0, 19), 1215 access_type: AccessType::Property, 1216 has_default: false, 1217 default_value: None, 1218 }); 1219 1220 graph.add_direct_reference(EnvReference { 1221 name: "VAR_TWO".into(), 1222 full_range: make_range(1, 0, 1, 20), 1223 name_range: make_range(1, 12, 1, 19), 1224 access_type: AccessType::Property, 1225 has_default: false, 1226 default_value: None, 1227 }); 1228 1229 let _id = graph.add_symbol(Symbol { 1230 id: SymbolId::new(1).unwrap(), 1231 name: "varThree".into(), 1232 declaration_range: make_range(2, 0, 2, 35), 1233 name_range: make_range(2, 6, 2, 14), 1234 scope: ScopeId::root(), 1235 origin: SymbolOrigin::EnvVar { 1236 name: "VAR_THREE".into(), 1237 }, 1238 kind: SymbolKind::Value, 1239 is_valid: true, 1240 destructured_key_range: None, 1241 }); 1242 1243 let resolver = BindingResolver::new(&graph); 1244 let all_vars = resolver.all_env_vars(); 1245 1246 assert_eq!(all_vars.len(), 3); 1247 assert!(all_vars.iter().any(|v| v == "VAR_ONE")); 1248 assert!(all_vars.iter().any(|v| v == "VAR_TWO")); 1249 assert!(all_vars.iter().any(|v| v == "VAR_THREE")); 1250 } 1251 1252 #[test] 1253 fn test_all_env_vars_empty() { 1254 let graph = BindingGraph::new(); 1255 let resolver = BindingResolver::new(&graph); 1256 1257 let all_vars = resolver.all_env_vars(); 1258 assert!(all_vars.is_empty()); 1259 } 1260 1261 #[test] 1262 fn test_all_env_vars_excludes_objects() { 1263 let mut graph = BindingGraph::new(); 1264 1265 let _id = graph.add_symbol(Symbol { 1266 id: SymbolId::new(1).unwrap(), 1267 name: "env".into(), 1268 declaration_range: make_range(0, 0, 0, 25), 1269 name_range: make_range(0, 6, 0, 9), 1270 scope: ScopeId::root(), 1271 origin: SymbolOrigin::EnvObject { 1272 canonical_name: "process.env".into(), 1273 }, 1274 kind: SymbolKind::EnvObject, 1275 is_valid: true, 1276 destructured_key_range: None, 1277 }); 1278 1279 let resolver = BindingResolver::new(&graph); 1280 let all_vars = resolver.all_env_vars(); 1281 1282 assert!(all_vars.is_empty()); 1283 } 1284 1285 #[test] 1286 fn test_get_symbol() { 1287 let mut graph = BindingGraph::new(); 1288 1289 let id = graph.add_symbol(Symbol { 1290 id: SymbolId::new(1).unwrap(), 1291 name: "testVar".into(), 1292 declaration_range: make_range(0, 0, 0, 20), 1293 name_range: make_range(0, 6, 0, 13), 1294 scope: ScopeId::root(), 1295 origin: SymbolOrigin::EnvVar { 1296 name: "TEST".into(), 1297 }, 1298 kind: SymbolKind::Value, 1299 is_valid: true, 1300 destructured_key_range: None, 1301 }); 1302 1303 let resolver = BindingResolver::new(&graph); 1304 1305 let symbol = resolver.get_symbol(id); 1306 assert!(symbol.is_some()); 1307 assert_eq!(symbol.unwrap().name, "testVar"); 1308 1309 let fake_id = SymbolId::new(999).unwrap(); 1310 assert!(resolver.get_symbol(fake_id).is_none()); 1311 } 1312 1313 #[test] 1314 fn test_lookup_symbol() { 1315 let mut graph = BindingGraph::new(); 1316 1317 let _id = graph.add_symbol(Symbol { 1318 id: SymbolId::new(1).unwrap(), 1319 name: "myVar".into(), 1320 declaration_range: make_range(0, 0, 0, 20), 1321 name_range: make_range(0, 6, 0, 11), 1322 scope: ScopeId::root(), 1323 origin: SymbolOrigin::EnvVar { 1324 name: "MY_VAR".into(), 1325 }, 1326 kind: SymbolKind::Value, 1327 is_valid: true, 1328 destructured_key_range: None, 1329 }); 1330 1331 let resolver = BindingResolver::new(&graph); 1332 1333 let symbol = resolver.lookup_symbol("myVar", ScopeId::root()); 1334 assert!(symbol.is_some()); 1335 assert_eq!(symbol.unwrap().name, "myVar"); 1336 1337 let not_found = resolver.lookup_symbol("nonexistent", ScopeId::root()); 1338 assert!(not_found.is_none()); 1339 } 1340 1341 #[test] 1342 fn test_scope_at_position() { 1343 let graph = BindingGraph::new(); 1344 let resolver = BindingResolver::new(&graph); 1345 1346 let scope = resolver.scope_at_position(Position::new(5, 10)); 1347 assert_eq!(scope, ScopeId::root()); 1348 } 1349 1350 #[test] 1351 fn test_is_env_object() { 1352 let mut graph = BindingGraph::new(); 1353 1354 let env_obj_id = graph.add_symbol(Symbol { 1355 id: SymbolId::new(1).unwrap(), 1356 name: "env".into(), 1357 declaration_range: make_range(0, 0, 0, 25), 1358 name_range: make_range(0, 6, 0, 9), 1359 scope: ScopeId::root(), 1360 origin: SymbolOrigin::EnvObject { 1361 canonical_name: "process.env".into(), 1362 }, 1363 kind: SymbolKind::EnvObject, 1364 is_valid: true, 1365 destructured_key_range: None, 1366 }); 1367 1368 let var_id = graph.add_symbol(Symbol { 1369 id: SymbolId::new(2).unwrap(), 1370 name: "dbUrl".into(), 1371 declaration_range: make_range(1, 0, 1, 35), 1372 name_range: make_range(1, 6, 1, 11), 1373 scope: ScopeId::root(), 1374 origin: SymbolOrigin::EnvVar { 1375 name: "DATABASE_URL".into(), 1376 }, 1377 kind: SymbolKind::Value, 1378 is_valid: true, 1379 destructured_key_range: None, 1380 }); 1381 1382 let resolver = BindingResolver::new(&graph); 1383 1384 assert!(resolver.is_env_object(env_obj_id)); 1385 assert!(!resolver.is_env_object(var_id)); 1386 } 1387 1388 #[test] 1389 fn test_get_env_reference_cloned() { 1390 let mut graph = BindingGraph::new(); 1391 1392 graph.add_direct_reference(EnvReference { 1393 name: "TEST_VAR".into(), 1394 full_range: make_range(0, 0, 0, 25), 1395 name_range: make_range(0, 12, 0, 20), 1396 access_type: AccessType::Property, 1397 has_default: true, 1398 default_value: Some("default".into()), 1399 }); 1400 1401 let resolver = BindingResolver::new(&graph); 1402 1403 let cloned = resolver.get_env_reference_cloned(Position::new(0, 15)); 1404 assert!(cloned.is_some()); 1405 let cloned = cloned.unwrap(); 1406 assert_eq!(cloned.name, "TEST_VAR"); 1407 assert!(cloned.has_default); 1408 assert_eq!(cloned.default_value, Some("default".into())); 1409 } 1410 1411 #[test] 1412 fn test_get_env_reference_cloned_from_property_access() { 1413 let mut graph = BindingGraph::new(); 1414 1415 let id = graph.add_symbol(Symbol { 1416 id: SymbolId::new(1).unwrap(), 1417 name: "env".into(), 1418 declaration_range: make_range(0, 0, 0, 25), 1419 name_range: make_range(0, 6, 0, 9), 1420 scope: ScopeId::root(), 1421 origin: SymbolOrigin::EnvObject { 1422 canonical_name: "process.env".into(), 1423 }, 1424 kind: SymbolKind::EnvObject, 1425 is_valid: true, 1426 destructured_key_range: None, 1427 }); 1428 1429 graph.add_usage(SymbolUsage { 1430 symbol_id: id, 1431 range: make_range(1, 0, 1, 10), 1432 scope: ScopeId::root(), 1433 property_access: Some("MY_VAR".into()), 1434 property_access_range: Some(make_range(1, 4, 1, 10)), 1435 }); 1436 graph.rebuild_range_index(); 1437 1438 let resolver = BindingResolver::new(&graph); 1439 1440 let cloned = resolver.get_env_reference_cloned(Position::new(1, 6)); 1441 assert!(cloned.is_some()); 1442 let cloned = cloned.unwrap(); 1443 assert_eq!(cloned.name, "MY_VAR"); 1444 assert_eq!(cloned.access_type, AccessType::Property); 1445 } 1446 1447 #[test] 1448 fn test_get_env_reference_cloned_returns_none_for_binding() { 1449 let mut graph = BindingGraph::new(); 1450 1451 let _id = graph.add_symbol(Symbol { 1452 id: SymbolId::new(1).unwrap(), 1453 name: "dbUrl".into(), 1454 declaration_range: make_range(0, 0, 0, 40), 1455 name_range: make_range(0, 6, 0, 11), 1456 scope: ScopeId::root(), 1457 origin: SymbolOrigin::EnvVar { 1458 name: "DATABASE_URL".into(), 1459 }, 1460 kind: SymbolKind::Value, 1461 is_valid: true, 1462 destructured_key_range: None, 1463 }); 1464 1465 let resolver = BindingResolver::new(&graph); 1466 1467 let cloned = resolver.get_env_reference_cloned(Position::new(0, 8)); 1468 assert!(cloned.is_none()); 1469 } 1470 1471 #[test] 1472 fn test_get_env_binding_cloned() { 1473 let mut graph = BindingGraph::new(); 1474 1475 let scope_id = graph.add_scope(crate::types::Scope { 1476 id: ScopeId::new(1).unwrap(), 1477 parent: Some(ScopeId::root()), 1478 range: make_range(0, 0, 10, 0), 1479 kind: ScopeKind::Function, 1480 }); 1481 1482 let _id = graph.add_symbol(Symbol { 1483 id: SymbolId::new(1).unwrap(), 1484 name: "myVar".into(), 1485 declaration_range: make_range(1, 0, 1, 35), 1486 name_range: make_range(1, 6, 1, 11), 1487 scope: scope_id, 1488 origin: SymbolOrigin::EnvVar { 1489 name: "MY_VAR".into(), 1490 }, 1491 kind: SymbolKind::Value, 1492 is_valid: true, 1493 destructured_key_range: Some(make_range(1, 6, 1, 11)), 1494 }); 1495 graph.rebuild_range_index(); 1496 1497 let resolver = BindingResolver::new(&graph); 1498 1499 let binding = resolver.get_env_binding_cloned(Position::new(1, 8)); 1500 assert!(binding.is_some()); 1501 let binding = binding.unwrap(); 1502 assert_eq!(binding.binding_name, "myVar"); 1503 assert_eq!(binding.env_var_name, "MY_VAR"); 1504 assert_eq!(binding.scope_range, make_range(0, 0, 10, 0)); 1505 assert!(binding.is_valid); 1506 assert_eq!( 1507 binding.destructured_key_range, 1508 Some(make_range(1, 6, 1, 11)) 1509 ); 1510 } 1511 1512 #[test] 1513 fn test_get_env_binding_cloned_returns_none_for_direct_ref() { 1514 let mut graph = BindingGraph::new(); 1515 1516 graph.add_direct_reference(EnvReference { 1517 name: "DIRECT".into(), 1518 full_range: make_range(0, 0, 0, 20), 1519 name_range: make_range(0, 12, 0, 18), 1520 access_type: AccessType::Property, 1521 has_default: false, 1522 default_value: None, 1523 }); 1524 1525 let resolver = BindingResolver::new(&graph); 1526 1527 let binding = resolver.get_env_binding_cloned(Position::new(0, 15)); 1528 assert!(binding.is_none()); 1529 } 1530 1531 #[test] 1532 fn test_get_binding_usage_cloned() { 1533 let mut graph = BindingGraph::new(); 1534 1535 let id = graph.add_symbol(Symbol { 1536 id: SymbolId::new(1).unwrap(), 1537 name: "usedVar".into(), 1538 declaration_range: make_range(0, 0, 0, 30), 1539 name_range: make_range(0, 6, 0, 13), 1540 scope: ScopeId::root(), 1541 origin: SymbolOrigin::EnvVar { 1542 name: "USED_VAR".into(), 1543 }, 1544 kind: SymbolKind::Value, 1545 is_valid: true, 1546 destructured_key_range: None, 1547 }); 1548 1549 graph.add_usage(SymbolUsage { 1550 symbol_id: id, 1551 range: make_range(2, 5, 2, 12), 1552 scope: ScopeId::root(), 1553 property_access: None, 1554 property_access_range: None, 1555 }); 1556 graph.rebuild_range_index(); 1557 1558 let resolver = BindingResolver::new(&graph); 1559 1560 let usage = resolver.get_binding_usage_cloned(Position::new(2, 8)); 1561 assert!(usage.is_some()); 1562 let usage = usage.unwrap(); 1563 assert_eq!(usage.name, "usedVar"); 1564 assert_eq!(usage.env_var_name, "USED_VAR"); 1565 assert_eq!(usage.range, make_range(2, 5, 2, 12)); 1566 } 1567 1568 #[test] 1569 fn test_get_binding_usage_cloned_returns_none_for_declaration() { 1570 let mut graph = BindingGraph::new(); 1571 1572 let _id = graph.add_symbol(Symbol { 1573 id: SymbolId::new(1).unwrap(), 1574 name: "declVar".into(), 1575 declaration_range: make_range(0, 0, 0, 30), 1576 name_range: make_range(0, 6, 0, 13), 1577 scope: ScopeId::root(), 1578 origin: SymbolOrigin::EnvVar { 1579 name: "DECL_VAR".into(), 1580 }, 1581 kind: SymbolKind::Value, 1582 is_valid: true, 1583 destructured_key_range: None, 1584 }); 1585 1586 let resolver = BindingResolver::new(&graph); 1587 1588 let usage = resolver.get_binding_usage_cloned(Position::new(0, 8)); 1589 assert!(usage.is_none()); 1590 } 1591 1592 #[test] 1593 fn test_get_binding_kind() { 1594 let mut graph = BindingGraph::new(); 1595 1596 let _id1 = graph.add_symbol(Symbol { 1597 id: SymbolId::new(1).unwrap(), 1598 name: "valueBinding".into(), 1599 declaration_range: make_range(0, 0, 0, 40), 1600 name_range: make_range(0, 6, 0, 18), 1601 scope: ScopeId::root(), 1602 origin: SymbolOrigin::EnvVar { 1603 name: "VALUE".into(), 1604 }, 1605 kind: SymbolKind::Value, 1606 is_valid: true, 1607 destructured_key_range: None, 1608 }); 1609 1610 let _id2 = graph.add_symbol(Symbol { 1611 id: SymbolId::new(2).unwrap(), 1612 name: "objectBinding".into(), 1613 declaration_range: make_range(1, 0, 1, 30), 1614 name_range: make_range(1, 6, 1, 19), 1615 scope: ScopeId::root(), 1616 origin: SymbolOrigin::EnvObject { 1617 canonical_name: "process.env".into(), 1618 }, 1619 kind: SymbolKind::EnvObject, 1620 is_valid: true, 1621 destructured_key_range: None, 1622 }); 1623 1624 let _id3 = graph.add_symbol(Symbol { 1625 id: SymbolId::new(3).unwrap(), 1626 name: "invalidBinding".into(), 1627 declaration_range: make_range(2, 0, 2, 40), 1628 name_range: make_range(2, 6, 2, 20), 1629 scope: ScopeId::root(), 1630 origin: SymbolOrigin::EnvVar { 1631 name: "INVALID".into(), 1632 }, 1633 kind: SymbolKind::Value, 1634 is_valid: false, 1635 destructured_key_range: None, 1636 }); 1637 1638 let resolver = BindingResolver::new(&graph); 1639 1640 let kind1 = resolver.get_binding_kind("valueBinding"); 1641 assert!(kind1.is_some()); 1642 assert_eq!(kind1.unwrap(), BindingKind::Value); 1643 1644 let kind2 = resolver.get_binding_kind("objectBinding"); 1645 assert!(kind2.is_some()); 1646 assert_eq!(kind2.unwrap(), BindingKind::Object); 1647 1648 let kind3 = resolver.get_binding_kind("invalidBinding"); 1649 assert!(kind3.is_none()); 1650 1651 let kind4 = resolver.get_binding_kind("nonexistent"); 1652 assert!(kind4.is_none()); 1653 } 1654 1655 #[test] 1656 fn test_usage_kind_equality() { 1657 assert_eq!(UsageKind::DirectReference, UsageKind::DirectReference); 1658 assert_eq!(UsageKind::BindingDeclaration, UsageKind::BindingDeclaration); 1659 assert_eq!(UsageKind::BindingUsage, UsageKind::BindingUsage); 1660 assert_eq!(UsageKind::PropertyAccess, UsageKind::PropertyAccess); 1661 1662 assert_ne!(UsageKind::DirectReference, UsageKind::BindingUsage); 1663 assert_ne!(UsageKind::BindingDeclaration, UsageKind::PropertyAccess); 1664 } 1665 1666 #[test] 1667 fn test_usage_kind_copy() { 1668 let kind = UsageKind::DirectReference; 1669 let copied = kind; 1670 assert_eq!(kind, copied); 1671 } 1672 1673 #[test] 1674 fn test_env_var_usage_location_clone() { 1675 let location = EnvVarUsageLocation { 1676 range: make_range(0, 0, 0, 10), 1677 kind: UsageKind::DirectReference, 1678 binding_name: Some("myBinding".into()), 1679 }; 1680 1681 let cloned = location.clone(); 1682 assert_eq!(cloned.range, location.range); 1683 assert_eq!(cloned.kind, location.kind); 1684 assert_eq!(cloned.binding_name, location.binding_name); 1685 } 1686 1687 #[test] 1688 fn test_env_var_usage_location_debug() { 1689 let location = EnvVarUsageLocation { 1690 range: make_range(1, 5, 1, 15), 1691 kind: UsageKind::BindingUsage, 1692 binding_name: None, 1693 }; 1694 1695 let debug_str = format!("{:?}", location); 1696 assert!(debug_str.contains("EnvVarUsageLocation")); 1697 assert!(debug_str.contains("BindingUsage")); 1698 } 1699 1700 #[test] 1701 fn test_complex_chain_resolution() { 1702 let mut graph = BindingGraph::new(); 1703 1704 let env_id = graph.add_symbol(Symbol { 1705 id: SymbolId::new(1).unwrap(), 1706 name: "env".into(), 1707 declaration_range: make_range(0, 0, 0, 25), 1708 name_range: make_range(0, 6, 0, 9), 1709 scope: ScopeId::root(), 1710 origin: SymbolOrigin::EnvObject { 1711 canonical_name: "process.env".into(), 1712 }, 1713 kind: SymbolKind::EnvObject, 1714 is_valid: true, 1715 destructured_key_range: None, 1716 }); 1717 1718 let config_id = graph.add_symbol(Symbol { 1719 id: SymbolId::new(2).unwrap(), 1720 name: "config".into(), 1721 declaration_range: make_range(1, 0, 1, 20), 1722 name_range: make_range(1, 6, 1, 12), 1723 scope: ScopeId::root(), 1724 origin: SymbolOrigin::Symbol { target: env_id }, 1725 kind: SymbolKind::Variable, 1726 is_valid: true, 1727 destructured_key_range: None, 1728 }); 1729 1730 let settings_id = graph.add_symbol(Symbol { 1731 id: SymbolId::new(3).unwrap(), 1732 name: "settings".into(), 1733 declaration_range: make_range(2, 0, 2, 25), 1734 name_range: make_range(2, 6, 2, 14), 1735 scope: ScopeId::root(), 1736 origin: SymbolOrigin::Symbol { target: config_id }, 1737 kind: SymbolKind::Variable, 1738 is_valid: true, 1739 destructured_key_range: None, 1740 }); 1741 1742 let _db_id = graph.add_symbol(Symbol { 1743 id: SymbolId::new(4).unwrap(), 1744 name: "DB_URL".into(), 1745 declaration_range: make_range(3, 0, 3, 30), 1746 name_range: make_range(3, 8, 3, 14), 1747 scope: ScopeId::root(), 1748 origin: SymbolOrigin::DestructuredProperty { 1749 source: settings_id, 1750 key: "DB_URL".into(), 1751 }, 1752 kind: SymbolKind::DestructuredProperty, 1753 is_valid: true, 1754 destructured_key_range: Some(make_range(3, 8, 3, 14)), 1755 }); 1756 graph.rebuild_range_index(); 1757 1758 let resolver = BindingResolver::new(&graph); 1759 1760 let hit = resolver.env_at_position(Position::new(3, 10)).unwrap(); 1761 assert_eq!(hit.env_var_name(), Some("DB_URL".into())); 1762 1763 let hit2 = resolver.env_at_position(Position::new(2, 10)).unwrap(); 1764 assert!(hit2.is_env_object()); 1765 assert_eq!(hit2.canonical_name().as_str(), "process.env"); 1766 } 1767 1768 #[test] 1769 fn test_multiple_env_vars_same_document() { 1770 let mut graph = BindingGraph::new(); 1771 1772 graph.add_direct_reference(EnvReference { 1773 name: "VAR_A".into(), 1774 full_range: make_range(0, 0, 0, 20), 1775 name_range: make_range(0, 12, 0, 17), 1776 access_type: AccessType::Property, 1777 has_default: false, 1778 default_value: None, 1779 }); 1780 1781 graph.add_direct_reference(EnvReference { 1782 name: "VAR_B".into(), 1783 full_range: make_range(1, 0, 1, 20), 1784 name_range: make_range(1, 12, 1, 17), 1785 access_type: AccessType::Property, 1786 has_default: false, 1787 default_value: None, 1788 }); 1789 1790 let _id = graph.add_symbol(Symbol { 1791 id: SymbolId::new(1).unwrap(), 1792 name: "varC".into(), 1793 declaration_range: make_range(2, 0, 2, 30), 1794 name_range: make_range(2, 6, 2, 10), 1795 scope: ScopeId::root(), 1796 origin: SymbolOrigin::EnvVar { 1797 name: "VAR_C".into(), 1798 }, 1799 kind: SymbolKind::Value, 1800 is_valid: true, 1801 destructured_key_range: None, 1802 }); 1803 graph.rebuild_range_index(); 1804 1805 let resolver = BindingResolver::new(&graph); 1806 1807 let hit_a = resolver.env_at_position(Position::new(0, 15)).unwrap(); 1808 assert_eq!(hit_a.canonical_name().as_str(), "VAR_A"); 1809 1810 let hit_b = resolver.env_at_position(Position::new(1, 15)).unwrap(); 1811 assert_eq!(hit_b.canonical_name().as_str(), "VAR_B"); 1812 1813 let hit_c = resolver.env_at_position(Position::new(2, 8)).unwrap(); 1814 assert_eq!(hit_c.canonical_name().as_str(), "VAR_C"); 1815 1816 let all_vars = resolver.all_env_vars(); 1817 assert_eq!(all_vars.len(), 3); 1818 } 1819 1820 #[test] 1821 fn test_binding_with_usage_at_different_scopes() { 1822 let mut graph = BindingGraph::new(); 1823 1824 let inner_scope = graph.add_scope(crate::types::Scope { 1825 id: ScopeId::new(1).unwrap(), 1826 parent: Some(ScopeId::root()), 1827 range: make_range(1, 0, 5, 0), 1828 kind: ScopeKind::Block, 1829 }); 1830 1831 let id = graph.add_symbol(Symbol { 1832 id: SymbolId::new(1).unwrap(), 1833 name: "rootVar".into(), 1834 declaration_range: make_range(0, 0, 0, 30), 1835 name_range: make_range(0, 6, 0, 13), 1836 scope: ScopeId::root(), 1837 origin: SymbolOrigin::EnvVar { 1838 name: "ROOT_VAR".into(), 1839 }, 1840 kind: SymbolKind::Value, 1841 is_valid: true, 1842 destructured_key_range: None, 1843 }); 1844 1845 graph.add_usage(SymbolUsage { 1846 symbol_id: id, 1847 range: make_range(2, 5, 2, 12), 1848 scope: inner_scope, 1849 property_access: None, 1850 property_access_range: None, 1851 }); 1852 graph.rebuild_range_index(); 1853 1854 let resolver = BindingResolver::new(&graph); 1855 1856 let hit = resolver.env_at_position(Position::new(2, 8)).unwrap(); 1857 assert!(matches!(hit, EnvHit::ViaUsage { .. })); 1858 assert_eq!(hit.env_var_name(), Some("ROOT_VAR".into())); 1859 } 1860 1861 #[test] 1862 fn test_binding_object_with_property_access_and_destructuring() { 1863 let mut graph = BindingGraph::new(); 1864 1865 let env_id = graph.add_symbol(Symbol { 1866 id: SymbolId::new(1).unwrap(), 1867 name: "env".into(), 1868 declaration_range: make_range(0, 0, 0, 25), 1869 name_range: make_range(0, 6, 0, 9), 1870 scope: ScopeId::root(), 1871 origin: SymbolOrigin::EnvObject { 1872 canonical_name: "process.env".into(), 1873 }, 1874 kind: SymbolKind::EnvObject, 1875 is_valid: true, 1876 destructured_key_range: None, 1877 }); 1878 1879 graph.add_usage(SymbolUsage { 1880 symbol_id: env_id, 1881 range: make_range(1, 0, 1, 11), 1882 scope: ScopeId::root(), 1883 property_access: Some("VAR_ONE".into()), 1884 property_access_range: Some(make_range(1, 4, 1, 11)), 1885 }); 1886 1887 let _var_two_id = graph.add_symbol(Symbol { 1888 id: SymbolId::new(2).unwrap(), 1889 name: "VAR_TWO".into(), 1890 declaration_range: make_range(2, 0, 2, 25), 1891 name_range: make_range(2, 8, 2, 15), 1892 scope: ScopeId::root(), 1893 origin: SymbolOrigin::DestructuredProperty { 1894 source: env_id, 1895 key: "VAR_TWO".into(), 1896 }, 1897 kind: SymbolKind::DestructuredProperty, 1898 is_valid: true, 1899 destructured_key_range: Some(make_range(2, 8, 2, 15)), 1900 }); 1901 1902 let resolver = BindingResolver::new(&graph); 1903 1904 let var_one_usages = resolver.find_env_var_usages("VAR_ONE"); 1905 assert_eq!(var_one_usages.len(), 1); 1906 assert_eq!(var_one_usages[0].kind, UsageKind::PropertyAccess); 1907 1908 let var_two_usages = resolver.find_env_var_usages("VAR_TWO"); 1909 assert_eq!(var_two_usages.len(), 1); 1910 assert_eq!(var_two_usages[0].kind, UsageKind::BindingDeclaration); 1911 } 1912 }