inlay_hints_test.rs
1 use crate::harness::{LspTestClient, TempWorkspace}; 2 use std::thread; 3 use std::time::Duration; 4 5 #[test] 6 fn test_inlay_hints_direct_reference() { 7 let workspace = TempWorkspace::new(); 8 workspace.create_config("[features]\ninlay_hints = true"); 9 let client = LspTestClient::spawn(workspace.root.clone()).expect("Failed to spawn LSP"); 10 client.initialize().expect("Initialize failed"); 11 12 let uri = workspace.file_uri("test.js"); 13 let content = "const url = process.env.DB_URL;"; 14 workspace.create_file("test.js", content); 15 16 client 17 .open_document(&uri, "javascript", content) 18 .expect("Failed to open document"); 19 20 thread::sleep(Duration::from_millis(300)); 21 22 let hints = client 23 .inlay_hint(&uri, 0, 0, 0, 50) 24 .expect("Inlay hint request failed"); 25 26 // Should return an array of hints 27 assert!(hints.is_array(), "Expected array of inlay hints"); 28 29 let hints_arr = hints.as_array().unwrap(); 30 assert!( 31 !hints_arr.is_empty(), 32 "Expected at least one inlay hint for DB_URL" 33 ); 34 35 // Check that the hint contains expected content 36 let first_hint = &hints_arr[0]; 37 let label = first_hint.get("label").expect("Missing label"); 38 let label_str = label.as_str().expect("Label not a string"); 39 assert!(label_str.contains('"'), "Hint should contain quoted value"); 40 41 client.shutdown().expect("Shutdown failed"); 42 } 43 44 #[test] 45 fn test_inlay_hints_destructuring() { 46 let workspace = TempWorkspace::new(); 47 workspace.create_config("[features]\ninlay_hints = true"); 48 let client = LspTestClient::spawn(workspace.root.clone()).expect("Failed to spawn LSP"); 49 client.initialize().expect("Initialize failed"); 50 51 let uri = workspace.file_uri("test.js"); 52 let content = "const { PORT, API_KEY } = process.env;"; 53 workspace.create_file("test.js", content); 54 55 client 56 .open_document(&uri, "javascript", content) 57 .expect("Failed to open document"); 58 59 thread::sleep(Duration::from_millis(300)); 60 61 let hints = client 62 .inlay_hint(&uri, 0, 0, 0, 50) 63 .expect("Inlay hint request failed"); 64 65 assert!(hints.is_array(), "Expected array of inlay hints"); 66 67 let hints_arr = hints.as_array().unwrap(); 68 // Should have hints for both PORT and API_KEY 69 assert!( 70 hints_arr.len() >= 2, 71 "Expected at least two inlay hints for destructuring" 72 ); 73 74 client.shutdown().expect("Shutdown failed"); 75 } 76 77 #[test] 78 #[ignore = "Alias property access not yet tracked in env_var_index for inlay hints"] 79 fn test_inlay_hints_alias_property_access() { 80 let workspace = TempWorkspace::new(); 81 workspace.create_config("[features]\ninlay_hints = true"); 82 let client = LspTestClient::spawn(workspace.root.clone()).expect("Failed to spawn LSP"); 83 client.initialize().expect("Initialize failed"); 84 85 let uri = workspace.file_uri("test.js"); 86 let content = "const e = process.env; const port = e.PORT;"; 87 workspace.create_file("test.js", content); 88 89 client 90 .open_document(&uri, "javascript", content) 91 .expect("Failed to open document"); 92 93 thread::sleep(Duration::from_millis(300)); 94 95 let hints = client 96 .inlay_hint(&uri, 0, 0, 0, 60) 97 .expect("Inlay hint request failed"); 98 99 assert!(hints.is_array(), "Expected array of inlay hints"); 100 101 let hints_arr = hints.as_array().unwrap(); 102 // Should have hint for PORT via property access on alias 103 assert!( 104 !hints_arr.is_empty(), 105 "Expected inlay hint for property access via alias" 106 ); 107 108 client.shutdown().expect("Shutdown failed"); 109 } 110 111 #[test] 112 fn test_inlay_hints_empty_for_no_env_vars() { 113 let workspace = TempWorkspace::new(); 114 workspace.create_config("[features]\ninlay_hints = true"); 115 let client = LspTestClient::spawn(workspace.root.clone()).expect("Failed to spawn LSP"); 116 client.initialize().expect("Initialize failed"); 117 118 let uri = workspace.file_uri("test.js"); 119 let content = "const x = 42; function foo() { return x * 2; }"; 120 workspace.create_file("test.js", content); 121 122 client 123 .open_document(&uri, "javascript", content) 124 .expect("Failed to open document"); 125 126 thread::sleep(Duration::from_millis(300)); 127 128 let hints = client 129 .inlay_hint(&uri, 0, 0, 0, 60) 130 .expect("Inlay hint request failed"); 131 132 assert!(hints.is_array(), "Expected array of inlay hints"); 133 134 let hints_arr = hints.as_array().unwrap(); 135 assert!( 136 hints_arr.is_empty(), 137 "Expected no inlay hints for code without env vars" 138 ); 139 140 client.shutdown().expect("Shutdown failed"); 141 } 142 143 #[test] 144 fn test_inlay_hints_tooltip_shows_source() { 145 let workspace = TempWorkspace::new(); 146 workspace.create_config("[features]\ninlay_hints = true"); 147 let client = LspTestClient::spawn(workspace.root.clone()).expect("Failed to spawn LSP"); 148 client.initialize().expect("Initialize failed"); 149 150 let uri = workspace.file_uri("test.js"); 151 let content = "const port = process.env.PORT;"; 152 workspace.create_file("test.js", content); 153 154 client 155 .open_document(&uri, "javascript", content) 156 .expect("Failed to open document"); 157 158 thread::sleep(Duration::from_millis(300)); 159 160 let hints = client 161 .inlay_hint(&uri, 0, 0, 0, 50) 162 .expect("Inlay hint request failed"); 163 164 assert!(hints.is_array(), "Expected array of inlay hints"); 165 166 let hints_arr = hints.as_array().unwrap(); 167 assert!(!hints_arr.is_empty(), "Expected at least one inlay hint"); 168 169 // Check that the tooltip contains source info 170 let first_hint = &hints_arr[0]; 171 let tooltip = first_hint.get("tooltip").expect("Missing tooltip"); 172 let tooltip_str = tooltip.as_str().expect("Tooltip not a string"); 173 assert!( 174 tooltip_str.contains("Source:"), 175 "Tooltip should indicate source. Got: {}", 176 tooltip_str 177 ); 178 179 client.shutdown().expect("Shutdown failed"); 180 } 181 182 #[test] 183 fn test_inlay_hints_typescript() { 184 let workspace = TempWorkspace::new(); 185 workspace.create_config("[features]\ninlay_hints = true"); 186 let client = LspTestClient::spawn(workspace.root.clone()).expect("Failed to spawn LSP"); 187 client.initialize().expect("Initialize failed"); 188 189 let uri = workspace.file_uri("test.ts"); 190 let content = "const port: string = process.env.PORT!;"; 191 workspace.create_file("test.ts", content); 192 193 client 194 .open_document(&uri, "typescript", content) 195 .expect("Failed to open document"); 196 197 thread::sleep(Duration::from_millis(300)); 198 199 let hints = client 200 .inlay_hint(&uri, 0, 0, 0, 50) 201 .expect("Inlay hint request failed"); 202 203 assert!( 204 hints.is_array(), 205 "Expected array of inlay hints for TypeScript" 206 ); 207 208 let hints_arr = hints.as_array().unwrap(); 209 assert!( 210 !hints_arr.is_empty(), 211 "Expected at least one inlay hint for TypeScript" 212 ); 213 214 client.shutdown().expect("Shutdown failed"); 215 } 216 217 #[test] 218 fn test_inlay_hints_python() { 219 let workspace = TempWorkspace::new(); 220 workspace.create_config("[features]\ninlay_hints = true"); 221 let client = LspTestClient::spawn(workspace.root.clone()).expect("Failed to spawn LSP"); 222 client.initialize().expect("Initialize failed"); 223 224 let uri = workspace.file_uri("test.py"); 225 let content = "import os\ndb = os.environ['DB_URL']"; 226 workspace.create_file("test.py", content); 227 228 client 229 .open_document(&uri, "python", content) 230 .expect("Failed to open document"); 231 232 thread::sleep(Duration::from_millis(300)); 233 234 let hints = client 235 .inlay_hint(&uri, 0, 0, 2, 0) 236 .expect("Inlay hint request failed"); 237 238 assert!(hints.is_array(), "Expected array of inlay hints for Python"); 239 240 let hints_arr = hints.as_array().unwrap(); 241 assert!( 242 !hints_arr.is_empty(), 243 "Expected at least one inlay hint for Python" 244 ); 245 246 client.shutdown().expect("Shutdown failed"); 247 } 248 249 #[test] 250 fn test_inlay_hints_range_filtering() { 251 let workspace = TempWorkspace::new(); 252 workspace.create_config("[features]\ninlay_hints = true"); 253 let client = LspTestClient::spawn(workspace.root.clone()).expect("Failed to spawn LSP"); 254 client.initialize().expect("Initialize failed"); 255 256 let uri = workspace.file_uri("test.js"); 257 // Multiple env vars on different lines 258 let content = "const a = process.env.PORT;\nconst b = process.env.DEBUG;"; 259 workspace.create_file("test.js", content); 260 261 client 262 .open_document(&uri, "javascript", content) 263 .expect("Failed to open document"); 264 265 thread::sleep(Duration::from_millis(300)); 266 267 // Request hints only for line 0 268 let hints = client 269 .inlay_hint(&uri, 0, 0, 0, 50) 270 .expect("Inlay hint request failed"); 271 272 assert!(hints.is_array(), "Expected array of inlay hints"); 273 274 let hints_arr = hints.as_array().unwrap(); 275 // Should only have hint for PORT (line 0), not DEBUG (line 1) 276 for hint in hints_arr { 277 let position = hint.get("position").expect("Missing position"); 278 let line = position 279 .get("line") 280 .expect("Missing line") 281 .as_u64() 282 .unwrap(); 283 assert_eq!( 284 line, 0, 285 "All hints should be on line 0 given the requested range" 286 ); 287 } 288 289 client.shutdown().expect("Shutdown failed"); 290 }