bindings_integration.rs
1 use abundantis::source::remote::ProviderManager; 2 use abundantis::Abundantis; 3 use ecolog_lsp::analysis::{ 4 DocumentManager, ModuleResolver, QueryEngine, WorkspaceIndex, WorkspaceIndexer, 5 }; 6 use ecolog_lsp::languages::LanguageRegistry; 7 use ecolog_lsp::server::config::ConfigManager; 8 use ecolog_lsp::server::handlers::{compute_diagnostics, handle_hover}; 9 use ecolog_lsp::server::state::ServerState; 10 use std::fs::{self, File}; 11 use std::io::Write; 12 use std::sync::Arc; 13 use std::time::{SystemTime, UNIX_EPOCH}; 14 use tower_lsp::lsp_types::{ 15 HoverParams, Position, TextDocumentIdentifier, TextDocumentPositionParams, Url, 16 }; 17 18 #[tokio::test] 19 async fn test_bindings_integration() { 20 let timestamp = SystemTime::now() 21 .duration_since(UNIX_EPOCH) 22 .unwrap() 23 .as_nanos(); 24 let temp_dir = std::env::temp_dir().join(format!("ecolog_integ_test_{}", timestamp)); 25 fs::create_dir_all(&temp_dir).unwrap(); 26 27 let env_path = temp_dir.join(".env"); 28 let mut env_file = File::create(&env_path).unwrap(); 29 writeln!(env_file, "DB_URL=postgres://localhost").unwrap(); 30 writeln!(env_file, "API_KEY=secret_key").unwrap(); 31 writeln!(env_file, "JSON_BLOB=some_data").unwrap(); 32 33 let mut registry = LanguageRegistry::new(); 34 registry.register(Arc::new(ecolog_lsp::languages::javascript::JavaScript)); 35 registry.register(Arc::new(ecolog_lsp::languages::typescript::TypeScript)); 36 registry.register(Arc::new(ecolog_lsp::languages::python::Python)); 37 let languages = Arc::new(registry); 38 39 let query_engine = Arc::new(QueryEngine::new()); 40 let document_manager = Arc::new(DocumentManager::new( 41 query_engine.clone(), 42 languages.clone(), 43 )); 44 let config_manager = Arc::new(ConfigManager::new()); 45 let core = Arc::new( 46 Abundantis::builder() 47 .root(&temp_dir) 48 .build() 49 .await 50 .expect("Failed to build Abundantis"), 51 ); 52 let workspace_index = Arc::new(WorkspaceIndex::new()); 53 let module_resolver = Arc::new(ModuleResolver::new(temp_dir.clone())); 54 let indexer = Arc::new(WorkspaceIndexer::new( 55 Arc::clone(&workspace_index), 56 query_engine, 57 Arc::clone(&languages), 58 temp_dir.clone(), 59 )); 60 61 let providers_config = abundantis::config::ProvidersConfig::default(); 62 let provider_manager = Arc::new(ProviderManager::new(providers_config)); 63 64 let state = ServerState::new( 65 document_manager, 66 languages, 67 core, 68 config_manager, 69 workspace_index, 70 indexer, 71 module_resolver, 72 provider_manager, 73 ); 74 75 let js_path = temp_dir.join("bracket.js"); 76 let js_content = r#" 77 const a = process.env['JSON_BLOB']; 78 a; 79 "#; 80 let mut f = File::create(&js_path).unwrap(); 81 write!(f, "{}", js_content).unwrap(); 82 let uri_js = Url::from_file_path(&js_path).unwrap(); 83 84 state 85 .document_manager 86 .open( 87 uri_js.clone(), 88 "javascript".to_string(), 89 js_content.to_string(), 90 0, 91 ) 92 .await; 93 tokio::time::sleep(std::time::Duration::from_millis(200)).await; 94 95 let hover = handle_hover( 96 HoverParams { 97 text_document_position_params: TextDocumentPositionParams { 98 text_document: TextDocumentIdentifier { 99 uri: uri_js.clone(), 100 }, 101 position: Position::new(2, 0), 102 }, 103 work_done_progress_params: Default::default(), 104 }, 105 &state, 106 ) 107 .await; 108 109 assert!(hover.is_some(), "JS Bracket Access Hover failed"); 110 assert!(format!("{:?}", hover.unwrap()).contains("JSON_BLOB")); 111 112 let ts_path = temp_dir.join("destruct.ts"); 113 let ts_content = r#" 114 const { API_KEY } = process.env; 115 API_KEY; 116 "#; 117 let mut f = File::create(&ts_path).unwrap(); 118 write!(f, "{}", ts_content).unwrap(); 119 let uri_ts = Url::from_file_path(&ts_path).unwrap(); 120 121 state 122 .document_manager 123 .open( 124 uri_ts.clone(), 125 "typescript".to_string(), 126 ts_content.to_string(), 127 0, 128 ) 129 .await; 130 tokio::time::sleep(std::time::Duration::from_millis(200)).await; 131 132 let hover = handle_hover( 133 HoverParams { 134 text_document_position_params: TextDocumentPositionParams { 135 text_document: TextDocumentIdentifier { 136 uri: uri_ts.clone(), 137 }, 138 position: Position::new(2, 0), 139 }, 140 work_done_progress_params: Default::default(), 141 }, 142 &state, 143 ) 144 .await; 145 146 assert!(hover.is_some(), "TS Destructuring Hover failed"); 147 assert!(format!("{:?}", hover.unwrap()).contains("API_KEY")); 148 149 let ts_bracket_path = temp_dir.join("bracket.ts"); 150 let ts_bracket_content = r#" 151 const b = process.env['JSON_BLOB']; 152 b; 153 "#; 154 let mut f = File::create(&ts_bracket_path).unwrap(); 155 write!(f, "{}", ts_bracket_content).unwrap(); 156 let uri_ts_bracket = Url::from_file_path(&ts_bracket_path).unwrap(); 157 158 state 159 .document_manager 160 .open( 161 uri_ts_bracket.clone(), 162 "typescript".to_string(), 163 ts_bracket_content.to_string(), 164 0, 165 ) 166 .await; 167 tokio::time::sleep(std::time::Duration::from_millis(200)).await; 168 169 let hover = handle_hover( 170 HoverParams { 171 text_document_position_params: TextDocumentPositionParams { 172 text_document: TextDocumentIdentifier { 173 uri: uri_ts_bracket.clone(), 174 }, 175 position: Position::new(2, 0), 176 }, 177 work_done_progress_params: Default::default(), 178 }, 179 &state, 180 ) 181 .await; 182 183 assert!(hover.is_some(), "TS Bracket Access Hover failed"); 184 assert!(format!("{:?}", hover.unwrap()).contains("JSON_BLOB")); 185 186 let scope_path = temp_dir.join("scope.js"); 187 let scope_content = r#" 188 function test() { 189 const secret = process.env.API_KEY; 190 } 191 secret; 192 "#; 193 let mut f = File::create(&scope_path).unwrap(); 194 write!(f, "{}", scope_content).unwrap(); 195 let uri_scope = Url::from_file_path(&scope_path).unwrap(); 196 197 state 198 .document_manager 199 .open( 200 uri_scope.clone(), 201 "javascript".to_string(), 202 scope_content.to_string(), 203 0, 204 ) 205 .await; 206 tokio::time::sleep(std::time::Duration::from_millis(200)).await; 207 208 let hover = handle_hover( 209 HoverParams { 210 text_document_position_params: TextDocumentPositionParams { 211 text_document: TextDocumentIdentifier { 212 uri: uri_scope.clone(), 213 }, 214 position: Position::new(4, 0), 215 }, 216 work_done_progress_params: Default::default(), 217 }, 218 &state, 219 ) 220 .await; 221 222 assert!( 223 hover.is_none(), 224 "Scope Isolation Failed: Should not hover out-of-scope variable" 225 ); 226 227 let alias_path = temp_dir.join("alias_msg.js"); 228 let alias_content = r#" 229 const env = process.env; 230 env; 231 "#; 232 let mut f = File::create(&alias_path).unwrap(); 233 write!(f, "{}", alias_content).unwrap(); 234 let uri_alias = Url::from_file_path(&alias_path).unwrap(); 235 236 state 237 .document_manager 238 .open( 239 uri_alias.clone(), 240 "javascript".to_string(), 241 alias_content.to_string(), 242 0, 243 ) 244 .await; 245 tokio::time::sleep(std::time::Duration::from_millis(200)).await; 246 247 let hover_decl = handle_hover( 248 HoverParams { 249 text_document_position_params: TextDocumentPositionParams { 250 text_document: TextDocumentIdentifier { 251 uri: uri_alias.clone(), 252 }, 253 position: Position::new(1, 6), 254 }, 255 work_done_progress_params: Default::default(), 256 }, 257 &state, 258 ) 259 .await; 260 261 println!("Alias Decl Hover: {:?}", hover_decl); 262 assert!( 263 hover_decl.is_some(), 264 "Expected hover for object alias declaration" 265 ); 266 let hover_str = format!("{:?}", hover_decl.unwrap()); 267 assert!( 268 hover_str.contains("Environment Object"), 269 "Detailed message should indicate Environment Object, got: {}", 270 hover_str 271 ); 272 assert!( 273 !hover_str.contains("(undefined)"), 274 "Should not show (undefined) for object alias" 275 ); 276 277 let _ = fs::remove_dir_all(&temp_dir); 278 } 279 280 #[tokio::test] 281 async fn test_destructuring_diagnostics() { 282 let timestamp = SystemTime::now() 283 .duration_since(UNIX_EPOCH) 284 .unwrap() 285 .as_nanos(); 286 let temp_dir = std::env::temp_dir().join(format!("ecolog_destruct_diag_{}", timestamp)); 287 fs::create_dir_all(&temp_dir).unwrap(); 288 289 let env_path = temp_dir.join(".env"); 290 let mut env_file = File::create(&env_path).unwrap(); 291 writeln!(env_file, "DB_URL=postgres://localhost").unwrap(); 292 293 let mut registry = LanguageRegistry::new(); 294 registry.register(Arc::new(ecolog_lsp::languages::javascript::JavaScript)); 295 let languages = Arc::new(registry); 296 297 let query_engine = Arc::new(QueryEngine::new()); 298 let document_manager = Arc::new(DocumentManager::new( 299 query_engine.clone(), 300 languages.clone(), 301 )); 302 let config_manager = Arc::new(ConfigManager::new()); 303 let core = Arc::new( 304 Abundantis::builder() 305 .root(&temp_dir) 306 .build() 307 .await 308 .expect("Failed to build Abundantis"), 309 ); 310 let workspace_index = Arc::new(WorkspaceIndex::new()); 311 let module_resolver = Arc::new(ModuleResolver::new(temp_dir.clone())); 312 let indexer = Arc::new(WorkspaceIndexer::new( 313 Arc::clone(&workspace_index), 314 query_engine, 315 Arc::clone(&languages), 316 temp_dir.clone(), 317 )); 318 319 let providers_config = abundantis::config::ProvidersConfig::default(); 320 let provider_manager = Arc::new(ProviderManager::new(providers_config)); 321 322 let state = ServerState::new( 323 document_manager, 324 languages, 325 core, 326 config_manager, 327 workspace_index, 328 indexer, 329 module_resolver, 330 provider_manager, 331 ); 332 333 let js_direct = temp_dir.join("direct.js"); 334 let js_direct_content = "const a = process.env.UNDEFINED_VAR;"; 335 let mut f = File::create(&js_direct).unwrap(); 336 write!(f, "{}", js_direct_content).unwrap(); 337 let uri_direct = Url::from_file_path(&js_direct).unwrap(); 338 339 state 340 .document_manager 341 .open( 342 uri_direct.clone(), 343 "javascript".to_string(), 344 js_direct_content.to_string(), 345 0, 346 ) 347 .await; 348 tokio::time::sleep(std::time::Duration::from_millis(100)).await; 349 350 let diagnostics_direct = compute_diagnostics(&uri_direct, &state).await; 351 println!("Direct access diagnostics: {:?}", diagnostics_direct); 352 assert!( 353 !diagnostics_direct.is_empty(), 354 "Should have diagnostic for undefined UNDEFINED_VAR in direct access" 355 ); 356 assert!( 357 diagnostics_direct 358 .iter() 359 .any(|d| d.message.contains("UNDEFINED_VAR")), 360 "Diagnostic should mention UNDEFINED_VAR" 361 ); 362 363 let js_destruct = temp_dir.join("destruct.js"); 364 let js_destruct_content = "const { UNDEFINED_VAR } = process.env;"; 365 let mut f = File::create(&js_destruct).unwrap(); 366 write!(f, "{}", js_destruct_content).unwrap(); 367 let uri_destruct = Url::from_file_path(&js_destruct).unwrap(); 368 369 state 370 .document_manager 371 .open( 372 uri_destruct.clone(), 373 "javascript".to_string(), 374 js_destruct_content.to_string(), 375 0, 376 ) 377 .await; 378 tokio::time::sleep(std::time::Duration::from_millis(100)).await; 379 380 let diagnostics_destruct = compute_diagnostics(&uri_destruct, &state).await; 381 println!("Destructuring diagnostics: {:?}", diagnostics_destruct); 382 assert!( 383 !diagnostics_destruct.is_empty(), 384 "Should have diagnostic for undefined UNDEFINED_VAR in destructuring" 385 ); 386 assert!( 387 diagnostics_destruct 388 .iter() 389 .any(|d| d.message.contains("UNDEFINED_VAR")), 390 "Diagnostic should mention UNDEFINED_VAR" 391 ); 392 393 let js_renamed = temp_dir.join("renamed.js"); 394 let js_renamed_content = "const { UNDEFINED_VAR: myVar } = process.env;"; 395 let mut f = File::create(&js_renamed).unwrap(); 396 write!(f, "{}", js_renamed_content).unwrap(); 397 let uri_renamed = Url::from_file_path(&js_renamed).unwrap(); 398 399 state 400 .document_manager 401 .open( 402 uri_renamed.clone(), 403 "javascript".to_string(), 404 js_renamed_content.to_string(), 405 0, 406 ) 407 .await; 408 tokio::time::sleep(std::time::Duration::from_millis(100)).await; 409 410 let diagnostics_renamed = compute_diagnostics(&uri_renamed, &state).await; 411 println!( 412 "Renamed destructuring diagnostics: {:?}", 413 diagnostics_renamed 414 ); 415 assert!( 416 !diagnostics_renamed.is_empty(), 417 "Should have diagnostic for undefined UNDEFINED_VAR in renamed destructuring" 418 ); 419 assert!( 420 diagnostics_renamed 421 .iter() 422 .any(|d| d.message.contains("UNDEFINED_VAR")), 423 "Diagnostic should mention UNDEFINED_VAR" 424 ); 425 426 let js_defined = temp_dir.join("defined.js"); 427 let js_defined_content = "const { DB_URL } = process.env;"; 428 let mut f = File::create(&js_defined).unwrap(); 429 write!(f, "{}", js_defined_content).unwrap(); 430 let uri_defined = Url::from_file_path(&js_defined).unwrap(); 431 432 state 433 .document_manager 434 .open( 435 uri_defined.clone(), 436 "javascript".to_string(), 437 js_defined_content.to_string(), 438 0, 439 ) 440 .await; 441 tokio::time::sleep(std::time::Duration::from_millis(100)).await; 442 443 let diagnostics_defined = compute_diagnostics(&uri_defined, &state).await; 444 println!("Defined var diagnostics: {:?}", diagnostics_defined); 445 assert!( 446 diagnostics_defined.is_empty() 447 || !diagnostics_defined 448 .iter() 449 .any(|d| d.message.contains("DB_URL")), 450 "Should NOT have diagnostic for defined DB_URL" 451 ); 452 453 let _ = fs::remove_dir_all(&temp_dir); 454 }