From 116e85c450dd81f63410b44cdd3875b228de5d71 Mon Sep 17 00:00:00 2001 From: Jorg Sowa Date: Fri, 13 Mar 2026 02:34:53 +0100 Subject: [PATCH] fix: support readonly modifier before visibility in class properties --- src/parser.js | 1 + src/parser/class.js | 11 +++-- .../snapshot/__snapshots__/class.test.js.snap | 48 +++++++++++++++++++ test/snapshot/class.test.js | 17 +++++++ 4 files changed, 74 insertions(+), 3 deletions(-) diff --git a/src/parser.js b/src/parser.js index 5e5ef1fa5..90a9917af 100644 --- a/src/parser.js +++ b/src/parser.js @@ -181,6 +181,7 @@ const Parser = function (lexer, ast) { this.tok.T_STATIC, this.tok.T_ABSTRACT, this.tok.T_FINAL, + this.tok.T_READ_ONLY, ].map(mapIt), ), EOS: new Map([";", this.EOF, this.tok.T_INLINE_HTML].map(mapIt)), diff --git a/src/parser/class.js b/src/parser/class.js index 29a45cb1f..fc8283345 100644 --- a/src/parser/class.js +++ b/src/parser/class.js @@ -190,8 +190,8 @@ module.exports = { */ function read_variable_declaration() { const result = this.node("property"); - let readonly = false; - if (this.token === this.tok.T_READ_ONLY) { + let readonly = flags[3] === 1; + if (!readonly && this.token === this.tok.T_READ_ONLY) { readonly = true; this.next(); } @@ -273,7 +273,7 @@ module.exports = { * 3rd index : 0 => normal, 1 => abstract member, 2 => final member */ read_member_flags(asInterface) { - const result = [-1, -1, -1]; + const result = [-1, -1, -1, -1]; if (this.is("T_MEMBER_FLAGS")) { let idx = 0, val = 0; @@ -303,6 +303,10 @@ module.exports = { idx = 2; val = 2; break; + case this.tok.T_READ_ONLY: + idx = 3; + val = 1; + break; } if (asInterface) { if (idx === 0 && val === 2) { @@ -326,6 +330,7 @@ module.exports = { if (result[1] === -1) result[1] = 0; if (result[2] === -1) result[2] = 0; + if (result[3] === -1) result[3] = 0; return result; }, diff --git a/test/snapshot/__snapshots__/class.test.js.snap b/test/snapshot/__snapshots__/class.test.js.snap index deca27ad0..b433c4663 100644 --- a/test/snapshot/__snapshots__/class.test.js.snap +++ b/test/snapshot/__snapshots__/class.test.js.snap @@ -562,6 +562,54 @@ Program { } `; +exports[`Test classes Implement readonly property with readonly before visibility 1`] = ` +Program { + "children": [ + Class { + "attrGroups": [], + "body": [ + PropertyStatement { + "isStatic": false, + "kind": "propertystatement", + "properties": [ + Property { + "attrGroups": [], + "kind": "property", + "name": Identifier { + "kind": "identifier", + "name": "test", + }, + "nullable": false, + "readonly": true, + "type": TypeReference { + "kind": "typereference", + "name": "int", + "raw": "int", + }, + "value": null, + }, + ], + "visibility": "public", + }, + ], + "extends": null, + "implements": null, + "isAbstract": false, + "isAnonymous": false, + "isFinal": false, + "isReadonly": false, + "kind": "class", + "name": Identifier { + "kind": "identifier", + "name": "Test", + }, + }, + ], + "errors": [], + "kind": "program", +} +`; + exports[`Test classes Implement typed_properties_v2 / php74 1`] = ` Program { "children": [ diff --git a/test/snapshot/class.test.js b/test/snapshot/class.test.js index 02eeb2a24..267bf156e 100644 --- a/test/snapshot/class.test.js +++ b/test/snapshot/class.test.js @@ -45,6 +45,23 @@ describe("Test classes", function () { ).toMatchSnapshot(); }); + it("Implement readonly property with readonly before visibility", function () { + expect( + parser.parseEval( + ` + class Test { + readonly public int $test; + } + `, + { + parser: { + version: "8.1", + }, + }, + ), + ).toMatchSnapshot(); + }); + it("Validate usual declarations", function () { expect( parser.parseEval(`