@@ -1297,6 +1297,121 @@ async fn test_completion_variable_used_in_different_contexts() {
12971297}
12981298
12991299/// Superglobals should not be duplicated even if they also appear in file content.
1300+ #[ tokio:: test]
1301+ async fn test_completion_foreach_variable_not_visible_after_loop ( ) {
1302+ let backend = create_test_backend ( ) ;
1303+
1304+ let uri = Url :: parse ( "file:///var_foreach_scope.php" ) . unwrap ( ) ;
1305+ let text = concat ! (
1306+ "<?php\n " ,
1307+ "$items = [1, 2, 3];\n " ,
1308+ "foreach ($items as $key => $value) {\n " ,
1309+ " echo $value;\n " ,
1310+ "}\n " ,
1311+ "$\n " ,
1312+ ) ;
1313+
1314+ // Cursor is after the foreach on line 5 — `$value` and `$key` should
1315+ // NOT appear because they are scoped to the loop body.
1316+ let items = complete_at ( & backend, & uri, text, 5 , 1 ) . await ;
1317+
1318+ let var_labels: Vec < & str > = items
1319+ . iter ( )
1320+ . filter ( |i| i. kind == Some ( CompletionItemKind :: VARIABLE ) )
1321+ . map ( |i| i. label . as_str ( ) )
1322+ . collect ( ) ;
1323+
1324+ assert ! (
1325+ !var_labels. contains( & "$value" ) ,
1326+ "$value should NOT be visible after the foreach loop. Got: {:?}" ,
1327+ var_labels
1328+ ) ;
1329+ assert ! (
1330+ !var_labels. contains( & "$key" ) ,
1331+ "$key should NOT be visible after the foreach loop. Got: {:?}" ,
1332+ var_labels
1333+ ) ;
1334+ assert ! (
1335+ var_labels. contains( & "$items" ) ,
1336+ "$items should still be visible after the foreach loop. Got: {:?}" ,
1337+ var_labels
1338+ ) ;
1339+ }
1340+
1341+ #[ tokio:: test]
1342+ async fn test_completion_foreach_variable_not_visible_before_loop ( ) {
1343+ let backend = create_test_backend ( ) ;
1344+
1345+ let uri = Url :: parse ( "file:///var_foreach_before.php" ) . unwrap ( ) ;
1346+ let text = concat ! (
1347+ "<?php\n " ,
1348+ "$items = [1, 2, 3];\n " ,
1349+ "$\n " ,
1350+ "foreach ($items as $value) {\n " ,
1351+ " echo $value;\n " ,
1352+ "}\n " ,
1353+ ) ;
1354+
1355+ // Cursor is before the foreach on line 2
1356+ let items = complete_at ( & backend, & uri, text, 2 , 1 ) . await ;
1357+
1358+ let var_labels: Vec < & str > = items
1359+ . iter ( )
1360+ . filter ( |i| i. kind == Some ( CompletionItemKind :: VARIABLE ) )
1361+ . map ( |i| i. label . as_str ( ) )
1362+ . collect ( ) ;
1363+
1364+ assert ! (
1365+ !var_labels. contains( & "$value" ) ,
1366+ "$value should NOT be visible before the foreach loop. Got: {:?}" ,
1367+ var_labels
1368+ ) ;
1369+ assert ! (
1370+ var_labels. contains( & "$items" ) ,
1371+ "$items should be visible before the foreach loop. Got: {:?}" ,
1372+ var_labels
1373+ ) ;
1374+ }
1375+
1376+ #[ tokio:: test]
1377+ async fn test_completion_foreach_variable_visible_inside_loop ( ) {
1378+ let backend = create_test_backend ( ) ;
1379+
1380+ let uri = Url :: parse ( "file:///var_foreach_inside.php" ) . unwrap ( ) ;
1381+ let text = concat ! (
1382+ "<?php\n " ,
1383+ "$items = [1, 2, 3];\n " ,
1384+ "foreach ($items as $key => $value) {\n " ,
1385+ " $\n " ,
1386+ "}\n " ,
1387+ ) ;
1388+
1389+ // Cursor is inside the foreach on line 3
1390+ let items = complete_at ( & backend, & uri, text, 3 , 5 ) . await ;
1391+
1392+ let var_labels: Vec < & str > = items
1393+ . iter ( )
1394+ . filter ( |i| i. kind == Some ( CompletionItemKind :: VARIABLE ) )
1395+ . map ( |i| i. label . as_str ( ) )
1396+ . collect ( ) ;
1397+
1398+ assert ! (
1399+ var_labels. contains( & "$value" ) ,
1400+ "$value should be visible inside the foreach loop. Got: {:?}" ,
1401+ var_labels
1402+ ) ;
1403+ assert ! (
1404+ var_labels. contains( & "$key" ) ,
1405+ "$key should be visible inside the foreach loop. Got: {:?}" ,
1406+ var_labels
1407+ ) ;
1408+ assert ! (
1409+ var_labels. contains( & "$items" ) ,
1410+ "$items should be visible inside the foreach loop. Got: {:?}" ,
1411+ var_labels
1412+ ) ;
1413+ }
1414+
13001415#[ tokio:: test]
13011416async fn test_completion_superglobal_not_duplicated ( ) {
13021417 let backend = create_test_backend ( ) ;
0 commit comments