@@ -1391,3 +1391,301 @@ async fn test_this_method_references_excludes_unrelated() {
13911391 lines
13921392 ) ;
13931393}
1394+
1395+ // ─── PHPDoc @property and @method References ────────────────────────────────
1396+
1397+ #[ tokio:: test]
1398+ async fn test_phpdoc_property_references_from_usage ( ) {
1399+ let backend = Backend :: new_test ( ) ;
1400+ let uri = Url :: parse ( "file:///test.php" ) . unwrap ( ) ;
1401+ let text = concat ! (
1402+ "<?php\n " , // L0
1403+ "/**\n " , // L1
1404+ " * @property string $email\n " , // L2
1405+ " */\n " , // L3
1406+ "class User {\n " , // L4
1407+ " public function demo(): void {\n " , // L5
1408+ " echo $this->email;\n " , // L6
1409+ " }\n " , // L7
1410+ "}\n " , // L8
1411+ "$u = new User();\n " , // L9
1412+ "echo $u->email;\n " , // L10
1413+ ) ;
1414+
1415+ open_file ( & backend, & uri, text) . await ;
1416+
1417+ // Click on "email" at line 10 ($u->email).
1418+ let locs = find_references ( & backend, & uri, 10 , 13 , true ) . await ;
1419+ assert ! (
1420+ locs. len( ) >= 3 ,
1421+ "Expected at least 3 references to email (declaration + 2 usages), got {}" ,
1422+ locs. len( )
1423+ ) ;
1424+
1425+ // Should include the @property declaration (line 2).
1426+ let has_declaration = locs. iter ( ) . any ( |l| l. range . start . line == 2 ) ;
1427+ assert ! (
1428+ has_declaration,
1429+ "Should include the @property declaration on line 2"
1430+ ) ;
1431+
1432+ // Should include the $this->email usage (line 6).
1433+ let has_this_usage = locs. iter ( ) . any ( |l| l. range . start . line == 6 ) ;
1434+ assert ! (
1435+ has_this_usage,
1436+ "Should include the $this->email usage on line 6"
1437+ ) ;
1438+
1439+ // Should include the $u->email usage (line 10).
1440+ let has_external_usage = locs. iter ( ) . any ( |l| l. range . start . line == 10 ) ;
1441+ assert ! (
1442+ has_external_usage,
1443+ "Should include the $u->email usage on line 10"
1444+ ) ;
1445+ }
1446+
1447+ #[ tokio:: test]
1448+ async fn test_phpdoc_property_references_from_declaration ( ) {
1449+ let backend = Backend :: new_test ( ) ;
1450+ let uri = Url :: parse ( "file:///test.php" ) . unwrap ( ) ;
1451+ let text = concat ! (
1452+ "<?php\n " , // L0
1453+ "/**\n " , // L1
1454+ " * @property string $email\n " , // L2
1455+ " */\n " , // L3
1456+ "class User {\n " , // L4
1457+ " public function demo(): void {\n " , // L5
1458+ " echo $this->email;\n " , // L6
1459+ " }\n " , // L7
1460+ "}\n " , // L8
1461+ "$u = new User();\n " , // L9
1462+ "echo $u->email;\n " , // L10
1463+ ) ;
1464+
1465+ open_file ( & backend, & uri, text) . await ;
1466+
1467+ // Click on "email" in the @property tag (line 2).
1468+ // Line: " * @property string $email"
1469+ // The MemberDeclaration span covers "email" (without $) starting at char 22.
1470+ let locs = find_references ( & backend, & uri, 2 , 22 , true ) . await ;
1471+ assert ! (
1472+ locs. len( ) >= 3 ,
1473+ "Expected at least 3 references from @property declaration, got {}" ,
1474+ locs. len( )
1475+ ) ;
1476+ }
1477+
1478+ #[ tokio:: test]
1479+ async fn test_phpdoc_method_references_from_usage ( ) {
1480+ let backend = Backend :: new_test ( ) ;
1481+ let uri = Url :: parse ( "file:///test.php" ) . unwrap ( ) ;
1482+ let text = concat ! (
1483+ "<?php\n " , // L0
1484+ "/**\n " , // L1
1485+ " * @method string getEmail()\n " , // L2
1486+ " */\n " , // L3
1487+ "class User {\n " , // L4
1488+ "}\n " , // L5
1489+ "$u = new User();\n " , // L6
1490+ "echo $u->getEmail();\n " , // L7
1491+ ) ;
1492+
1493+ open_file ( & backend, & uri, text) . await ;
1494+
1495+ // Click on "getEmail" at line 7 ($u->getEmail()).
1496+ let locs = find_references ( & backend, & uri, 7 , 10 , true ) . await ;
1497+ assert ! (
1498+ locs. len( ) >= 2 ,
1499+ "Expected at least 2 references to getEmail (declaration + usage), got {}" ,
1500+ locs. len( )
1501+ ) ;
1502+
1503+ // Should include the @method declaration (line 2).
1504+ let has_declaration = locs. iter ( ) . any ( |l| l. range . start . line == 2 ) ;
1505+ assert ! (
1506+ has_declaration,
1507+ "Should include the @method declaration on line 2"
1508+ ) ;
1509+ }
1510+
1511+ #[ tokio:: test]
1512+ async fn test_phpdoc_property_references_exclude_unrelated_class ( ) {
1513+ let backend = Backend :: new_test ( ) ;
1514+ let uri = Url :: parse ( "file:///test.php" ) . unwrap ( ) ;
1515+ let text = concat ! (
1516+ "<?php\n " , // L0
1517+ "/**\n " , // L1
1518+ " * @property string $email\n " , // L2
1519+ " */\n " , // L3
1520+ "class User {}\n " , // L4
1521+ "/**\n " , // L5
1522+ " * @property int $email\n " , // L6
1523+ " */\n " , // L7
1524+ "class Order {}\n " , // L8
1525+ "$u = new User();\n " , // L9
1526+ "echo $u->email;\n " , // L10
1527+ "$o = new Order();\n " , // L11
1528+ "echo $o->email;\n " , // L12
1529+ ) ;
1530+
1531+ open_file ( & backend, & uri, text) . await ;
1532+
1533+ // Click on "email" at line 10 ($u->email).
1534+ let locs = find_references ( & backend, & uri, 10 , 13 , true ) . await ;
1535+
1536+ // Should include User's @property and $u->email, but NOT Order's @property or $o->email.
1537+ let has_user_declaration = locs. iter ( ) . any ( |l| l. range . start . line == 2 ) ;
1538+ let has_user_usage = locs. iter ( ) . any ( |l| l. range . start . line == 10 ) ;
1539+ let has_order_declaration = locs. iter ( ) . any ( |l| l. range . start . line == 6 ) ;
1540+ let has_order_usage = locs. iter ( ) . any ( |l| l. range . start . line == 12 ) ;
1541+
1542+ assert ! (
1543+ has_user_declaration,
1544+ "Should include User's @property declaration"
1545+ ) ;
1546+ assert ! ( has_user_usage, "Should include $u->email usage" ) ;
1547+ assert ! (
1548+ !has_order_declaration,
1549+ "Should NOT include Order's @property declaration"
1550+ ) ;
1551+ assert ! ( !has_order_usage, "Should NOT include $o->email usage" ) ;
1552+ }
1553+
1554+ #[ tokio:: test]
1555+ async fn test_phpdoc_property_multiple_properties ( ) {
1556+ let backend = Backend :: new_test ( ) ;
1557+ let uri = Url :: parse ( "file:///test.php" ) . unwrap ( ) ;
1558+ let text = concat ! (
1559+ "<?php\n " , // L0
1560+ "/**\n " , // L1
1561+ " * @property int $id\n " , // L2
1562+ " * @property string $email\n " , // L3
1563+ " * @property string $name\n " , // L4
1564+ " */\n " , // L5
1565+ "class User {}\n " , // L6
1566+ "$u = new User();\n " , // L7
1567+ "echo $u->email;\n " , // L8
1568+ ) ;
1569+
1570+ open_file ( & backend, & uri, text) . await ;
1571+
1572+ // Click on "email" at line 8 ($u->email).
1573+ let locs = find_references ( & backend, & uri, 8 , 13 , true ) . await ;
1574+ assert ! (
1575+ locs. len( ) >= 2 ,
1576+ "Expected at least 2 references to email, got {}" ,
1577+ locs. len( )
1578+ ) ;
1579+
1580+ // Should include only the @property string $email declaration (line 3), not id or name.
1581+ let has_email_decl = locs. iter ( ) . any ( |l| l. range . start . line == 3 ) ;
1582+ let has_id_decl = locs. iter ( ) . any ( |l| l. range . start . line == 2 ) ;
1583+ let has_name_decl = locs. iter ( ) . any ( |l| l. range . start . line == 4 ) ;
1584+ assert ! (
1585+ has_email_decl,
1586+ "Should include @property string $email declaration"
1587+ ) ;
1588+ assert ! (
1589+ !has_id_decl,
1590+ "Should NOT include @property int $id declaration"
1591+ ) ;
1592+ assert ! (
1593+ !has_name_decl,
1594+ "Should NOT include @property string $name declaration"
1595+ ) ;
1596+ }
1597+
1598+ #[ tokio:: test]
1599+ async fn test_phpdoc_property_read_write_variants ( ) {
1600+ let backend = Backend :: new_test ( ) ;
1601+ let uri = Url :: parse ( "file:///test.php" ) . unwrap ( ) ;
1602+ let text = concat ! (
1603+ "<?php\n " , // L0
1604+ "/**\n " , // L1
1605+ " * @property-read string $name\n " , // L2
1606+ " * @property-write int $age\n " , // L3
1607+ " */\n " , // L4
1608+ "class User {}\n " , // L5
1609+ "$u = new User();\n " , // L6
1610+ "echo $u->name;\n " , // L7
1611+ ) ;
1612+
1613+ open_file ( & backend, & uri, text) . await ;
1614+
1615+ // Click on "name" at line 7 ($u->name).
1616+ let locs = find_references ( & backend, & uri, 7 , 13 , true ) . await ;
1617+ assert ! (
1618+ locs. len( ) >= 2 ,
1619+ "Expected at least 2 references to name (property-read declaration + usage), got {}" ,
1620+ locs. len( )
1621+ ) ;
1622+
1623+ // Should include the @property-read declaration (line 2).
1624+ let has_read_decl = locs. iter ( ) . any ( |l| l. range . start . line == 2 ) ;
1625+ assert ! (
1626+ has_read_decl,
1627+ "Should include the @property-read declaration"
1628+ ) ;
1629+ }
1630+
1631+ #[ tokio:: test]
1632+ async fn test_phpdoc_method_references_from_declaration ( ) {
1633+ let backend = Backend :: new_test ( ) ;
1634+ let uri = Url :: parse ( "file:///test.php" ) . unwrap ( ) ;
1635+ let text = concat ! (
1636+ "<?php\n " , // L0
1637+ "/**\n " , // L1
1638+ " * @method string getEmail()\n " , // L2
1639+ " */\n " , // L3
1640+ "class User {}\n " , // L4
1641+ "$u = new User();\n " , // L5
1642+ "echo $u->getEmail();\n " , // L6
1643+ ) ;
1644+
1645+ open_file ( & backend, & uri, text) . await ;
1646+
1647+ // Click on "getEmail" in the @method tag (line 2).
1648+ // Line: " * @method string getEmail()"
1649+ // The MemberDeclaration span covers "getEmail" starting at char 19.
1650+ let locs = find_references ( & backend, & uri, 2 , 19 , true ) . await ;
1651+ assert ! (
1652+ locs. len( ) >= 2 ,
1653+ "Expected at least 2 references from @method declaration, got {}" ,
1654+ locs. len( )
1655+ ) ;
1656+
1657+ let has_usage = locs. iter ( ) . any ( |l| l. range . start . line == 6 ) ;
1658+ assert ! ( has_usage, "Should include $u->getEmail() usage on line 6" ) ;
1659+ }
1660+
1661+ #[ tokio:: test]
1662+ async fn test_phpdoc_method_no_return_type_references ( ) {
1663+ let backend = Backend :: new_test ( ) ;
1664+ let uri = Url :: parse ( "file:///test.php" ) . unwrap ( ) ;
1665+ let text = concat ! (
1666+ "<?php\n " , // L0
1667+ "/**\n " , // L1
1668+ " * @method getEmail()\n " , // L2
1669+ " */\n " , // L3
1670+ "class User {}\n " , // L4
1671+ "$u = new User();\n " , // L5
1672+ "echo $u->getEmail();\n " , // L6
1673+ ) ;
1674+
1675+ open_file ( & backend, & uri, text) . await ;
1676+
1677+ // Click on "getEmail" at line 6 ($u->getEmail()).
1678+ let locs = find_references ( & backend, & uri, 6 , 10 , true ) . await ;
1679+ assert ! (
1680+ locs. len( ) >= 2 ,
1681+ "Expected at least 2 references to getEmail (declaration + usage), got {}" ,
1682+ locs. len( )
1683+ ) ;
1684+
1685+ // Should include the @method declaration (line 2).
1686+ let has_declaration = locs. iter ( ) . any ( |l| l. range . start . line == 2 ) ;
1687+ assert ! (
1688+ has_declaration,
1689+ "Should include the @method declaration on line 2"
1690+ ) ;
1691+ }
0 commit comments