diff --git a/pyproject.toml b/pyproject.toml index 799aa736b4..ff7e436e90 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -37,7 +37,8 @@ dependencies = [ "tree-sitter-c-sharp>=0.23,<0.25", "tree-sitter-kotlin>=1.0,<2.0", "tree-sitter-scala>=0.23,<0.27", - "tree-sitter-php>=0.23,<0.25", + # Fork commit until tree-sitter/tree-sitter-php#307 ships: keyword-named class constants (#17). + "tree-sitter-php @ git+https://github.com/alexwrite/tree-sitter-php@9678351433dfbcbd43bddfc614927f3dd7e33501", "tree-sitter-swift>=0.7,<0.9", "tree-sitter-lua>=0.2,<0.6", "tree-sitter-zig>=1.0,<2.0", diff --git a/tests/test_php_keyword_constant_names.py b/tests/test_php_keyword_constant_names.py new file mode 100644 index 0000000000..3347ba2658 --- /dev/null +++ b/tests/test_php_keyword_constant_names.py @@ -0,0 +1,85 @@ +"""PHP class constants named after reserved keywords must parse (#17). + +PHP allows any keyword as a class constant name (``public const string DEFAULT``). +tree-sitter-php up to 0.25.1 rejects most of them once the constant is typed +(PHP 8.3), and a handful (``VOID``, ``TRUE``...) even untyped +(tree-sitter/tree-sitter-php#295). Error recovery then swallows the whole class +declaration: the class vanishes from the graph and every ``Foo::DEFAULT`` access +loses its ``references_constant`` edge. +""" +from __future__ import annotations + +from pathlib import Path + +import pytest + +from graphify.extract import extract, extract_php + +_CORPUS = """ Path: + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(text, encoding="utf-8") + return path + + +def _node_id(result: dict, label: str) -> str | None: + return next( + (n["id"] for n in result["nodes"] if n.get("label") == label and n.get("source_file")), + None, + ) + + +@pytest.mark.parametrize( + "declaration", + [ + "public const string DEFAULT = 'a';", + "final protected const int PRIVATE = 1;", + "const ?string CLASS = null;", + "public const string|int FUNCTION = 2;", + "public const string NEW = 'b';", + "const VOID = 3;", + "const TRUE = true;", + ], +) +def test_keyword_named_constant_parses_cleanly(tmp_path: Path, declaration: str) -> None: + src = _write(tmp_path / "Holder.php", f" None: + src = _write(tmp_path / "src/Http/HttpAgent.php", _CORPUS) + + result = extract([src], cache_root=tmp_path / "graphify-out") + + agent_id = _node_id(result, "HttpAgent") + assert agent_id is not None, "HttpAgent declaration node is missing" + assert any( + e.get("relation") == "references_constant" and e["target"] == agent_id + for e in result["edges"] + ), "Client::agent() lost its references_constant edge to HttpAgent" diff --git a/uv.lock b/uv.lock index fd171a5a93..fd6e8052ba 100644 --- a/uv.lock +++ b/uv.lock @@ -1375,7 +1375,7 @@ requires-dist = [ { name = "tree-sitter-ocaml", marker = "extra == 'ocaml'" }, { name = "tree-sitter-pascal", marker = "extra == 'all'" }, { name = "tree-sitter-pascal", marker = "extra == 'pascal'" }, - { name = "tree-sitter-php", specifier = ">=0.23,<0.25" }, + { name = "tree-sitter-php", git = "https://github.com/alexwrite/tree-sitter-php?rev=9678351433dfbcbd43bddfc614927f3dd7e33501" }, { name = "tree-sitter-powershell", specifier = ">=0.26,<0.28" }, { name = "tree-sitter-python", specifier = ">=0.23,<0.26" }, { name = "tree-sitter-ruby", specifier = ">=0.23,<0.25" }, @@ -4961,17 +4961,8 @@ wheels = [ [[package]] name = "tree-sitter-php" -version = "0.24.1" -source = { registry = "https://pypi.org/simple" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a4/c8/1a499038cb4036bea1d560ffbc807a6fb940261aa22296bd49a62ed8bcba/tree_sitter_php-0.24.1-cp310-abi3-macosx_10_9_x86_64.whl", hash = "sha256:d56e2dcf025450f84a2cdbf4b18a09e6cb88b92e9e6858e63de3d4133ab2e43e", size = 219550, upload-time = "2025-08-16T22:14:30.212Z" }, - { url = "https://files.pythonhosted.org/packages/ab/5e/b52f2599acb29f6899470f7137d3d491c752b88df3950fb7408aea57ddca/tree_sitter_php-0.24.1-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:29759c67d4c27a68c227ed82c0b7e4699617b1bd23757d50c081f81a12b4f80d", size = 229632, upload-time = "2025-08-16T22:14:31.85Z" }, - { url = "https://files.pythonhosted.org/packages/6b/58/ca290da45380bd6ba7c6b0b98cc5fc30325c32c7f14f0c93196a451b19c4/tree_sitter_php-0.24.1-cp310-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:94b89832ac09f078eed2acd88598838bc51012224cbcebb916dbb6a37e74357e", size = 325351, upload-time = "2025-08-16T22:14:33Z" }, - { url = "https://files.pythonhosted.org/packages/9a/c6/fd863a7a779d0ab67688939eba0e08bff7b1ffe731288d3d3610df21217b/tree_sitter_php-0.24.1-cp310-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7a1404a30f2972498ace040b0029738b8dac45d0a12932ccb8b605eb94bafbe4", size = 313021, upload-time = "2025-08-16T22:14:34.394Z" }, - { url = "https://files.pythonhosted.org/packages/48/ed/aace12f30c4f5474a9ad0e9da85c060174e3764342c9860974bb0feb02fc/tree_sitter_php-0.24.1-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:3e96f61462a960c78e5389c7ba6c16c25e66b465c763b8e63ad66423326c2fa7", size = 305905, upload-time = "2025-08-16T22:14:35.846Z" }, - { url = "https://files.pythonhosted.org/packages/4e/c4/6c690c33b1ae9cae9505c0a2896f046fda174d72c46bdafce6aab3b2f2e7/tree_sitter_php-0.24.1-cp310-abi3-win_amd64.whl", hash = "sha256:1a1b65b72a8410d421f914ee13d38fd546a94d01cb834f69b27c78ba7589a5b5", size = 208014, upload-time = "2025-08-16T22:14:37.206Z" }, - { url = "https://files.pythonhosted.org/packages/7b/69/54c670d725c092b89e76ca6984582b6a768b128ac1859ed48141b124da1d/tree_sitter_php-0.24.1-cp310-abi3-win_arm64.whl", hash = "sha256:56a70c5ef1bddb15f220a479b2f2edf3042c764b6c443921fbd7ca9174d664e3", size = 206033, upload-time = "2025-08-16T22:14:38.632Z" }, -] +version = "0.25.2" +source = { git = "https://github.com/alexwrite/tree-sitter-php?rev=9678351433dfbcbd43bddfc614927f3dd7e33501#9678351433dfbcbd43bddfc614927f3dd7e33501" } [[package]] name = "tree-sitter-powershell"