From 1af89061b3e177b239b1b102ff1d115a87dfb2f1 Mon Sep 17 00:00:00 2001 From: Thomas Genin Date: Sun, 24 Nov 2024 21:29:55 -0800 Subject: [PATCH 01/17] consistent return value for class property parsing --- src/parser/class.js | 16 ++++++---------- test/snapshot/__snapshots__/class.test.js.snap | 8 ++++---- .../snapshot/__snapshots__/graceful.test.js.snap | 16 ++++++++-------- 3 files changed, 18 insertions(+), 22 deletions(-) diff --git a/src/parser/class.js b/src/parser/class.js index 996f5acf3..0a0fed513 100644 --- a/src/parser/class.js +++ b/src/parser/class.js @@ -201,22 +201,18 @@ module.exports = { const name = this.text().substring(1); // ignore $ this.next(); propName = propName(name); + + let value = null; + if (this.token === ";" || this.token === ",") { - return result(propName, null, readonly, nullable, type, attrs || []); + // no-op } else if (this.token === "=") { // https://github.com/php/php-src/blob/master/Zend/zend_language_parser.y#L815 - return result( - propName, - this.next().read_expr(), - readonly, - nullable, - type, - attrs || [], - ); + value = this.next().read_expr(); } else { this.expect([",", ";", "="]); - return result(propName, null, nullable, type, attrs || []); } + return result(propName, value, readonly, nullable, type, attrs || []); }, ",", ); diff --git a/test/snapshot/__snapshots__/class.test.js.snap b/test/snapshot/__snapshots__/class.test.js.snap index 6fd21a3aa..d408ec7d5 100644 --- a/test/snapshot/__snapshots__/class.test.js.snap +++ b/test/snapshot/__snapshots__/class.test.js.snap @@ -1200,19 +1200,19 @@ Program { "kind": "propertystatement", "properties": [ Property { - "attrGroups": null, + "attrGroups": [], "kind": "property", "name": Identifier { "kind": "identifier", "name": "id", }, - "nullable": TypeReference { + "nullable": false, + "readonly": true, + "type": TypeReference { "kind": "typereference", "name": "int", "raw": "int", }, - "readonly": false, - "type": [], "value": null, }, ], diff --git a/test/snapshot/__snapshots__/graceful.test.js.snap b/test/snapshot/__snapshots__/graceful.test.js.snap index cf45e3329..d1bf9b28c 100644 --- a/test/snapshot/__snapshots__/graceful.test.js.snap +++ b/test/snapshot/__snapshots__/graceful.test.js.snap @@ -300,19 +300,19 @@ Program { "kind": "propertystatement", "properties": [ Property { - "attrGroups": null, + "attrGroups": [], "kind": "property", "name": Identifier { "kind": "identifier", "name": "onst", }, - "nullable": Name { + "nullable": false, + "readonly": false, + "type": Name { "kind": "name", "name": "foo", "resolution": "uqn", }, - "readonly": false, - "type": [], "value": null, }, ], @@ -843,19 +843,19 @@ Program { "kind": "propertystatement", "properties": [ Property { - "attrGroups": null, + "attrGroups": [], "kind": "property", "name": Identifier { "kind": "identifier", "name": "mplement", }, - "nullable": Name { + "nullable": false, + "readonly": false, + "type": Name { "kind": "name", "name": "bar", "resolution": "uqn", }, - "readonly": false, - "type": [], "value": null, }, ], From 140206ef357d42d09913a87607eabd6feba110a8 Mon Sep 17 00:00:00 2001 From: Thomas Genin Date: Fri, 29 Nov 2024 16:24:41 -0800 Subject: [PATCH 02/17] x --- src/parser/class.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/parser/class.js b/src/parser/class.js index 0a0fed513..f8ecee5d4 100644 --- a/src/parser/class.js +++ b/src/parser/class.js @@ -210,7 +210,7 @@ module.exports = { // https://github.com/php/php-src/blob/master/Zend/zend_language_parser.y#L815 value = this.next().read_expr(); } else { - this.expect([",", ";", "="]); + this.expect([",", ";"]); } return result(propName, value, readonly, nullable, type, attrs || []); }, From 94e57ba7c3123299965a605567f168770c3d2674 Mon Sep 17 00:00:00 2001 From: Thomas Genin Date: Sat, 30 Nov 2024 14:40:26 -0800 Subject: [PATCH 03/17] simpler if/else --- src/parser/class.js | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/parser/class.js b/src/parser/class.js index f8ecee5d4..ccb4e9414 100644 --- a/src/parser/class.js +++ b/src/parser/class.js @@ -204,13 +204,10 @@ module.exports = { let value = null; - if (this.token === ";" || this.token === ",") { - // no-op - } else if (this.token === "=") { + this.expect([",", ";", "="]); + if (this.token === "=") { // https://github.com/php/php-src/blob/master/Zend/zend_language_parser.y#L815 value = this.next().read_expr(); - } else { - this.expect([",", ";"]); } return result(propName, value, readonly, nullable, type, attrs || []); }, From ea00ff10f54eba4d99ebb340025570f6a3cd56af Mon Sep 17 00:00:00 2001 From: Thomas Genin Date: Sun, 1 Dec 2024 13:06:38 -0800 Subject: [PATCH 04/17] Property Hook Getter works --- src/ast.js | 2 + src/ast/property.js | 3 + src/ast/propertyhooks.js | 26 +++ src/parser/class.js | 73 +++++++- test/snapshot/__snapshots__/acid.test.js.snap | 1 + .../__snapshots__/attributes.test.js.snap | 4 + .../snapshot/__snapshots__/class.test.js.snap | 167 ++---------------- .../classpropertyhooks.test.js.snap | 165 +++++++++++++++++ .../__snapshots__/comment.test.js.snap | 3 + .../__snapshots__/graceful.test.js.snap | 122 +++++++++++-- .../__snapshots__/heredoc.test.js.snap | 1 + .../__snapshots__/namespace.test.js.snap | 2 + .../__snapshots__/nowdoc.test.js.snap | 1 + .../__snapshots__/property.test.js.snap | 22 +++ .../propertystatement.test.js.snap | 8 + test/snapshot/class.test.js | 18 +- test/snapshot/classpropertyhooks.test.js | 65 +++++++ 17 files changed, 497 insertions(+), 186 deletions(-) create mode 100644 src/ast/propertyhooks.js create mode 100644 test/snapshot/__snapshots__/classpropertyhooks.test.js.snap create mode 100644 test/snapshot/classpropertyhooks.test.js diff --git a/src/ast.js b/src/ast.js index d11b556b5..4d9d0426d 100644 --- a/src/ast.js +++ b/src/ast.js @@ -106,6 +106,7 @@ const Position = require("./ast/position"); * - [Namespace](#namespace) * - [PropertyStatement](#propertystatement) * - [Property](#property) + * - [PropertyHooks](#propertyhooks) * - [Declaration](#declaration) * - [Class](#class) * - [Interface](#interface) @@ -552,6 +553,7 @@ AST.prototype.checkNodes = function () { require("./ast/print"), require("./ast/program"), require("./ast/property"), + require("./ast/propertyhooks"), require("./ast/propertylookup"), require("./ast/propertystatement"), require("./ast/reference"), diff --git a/src/ast/property.js b/src/ast/property.js index ad01986ce..cdb805c7d 100644 --- a/src/ast/property.js +++ b/src/ast/property.js @@ -18,6 +18,7 @@ const KIND = "property"; * @property {boolean} readonly * @property {boolean} nullable * @property {Identifier|Array|null} type + * @propert {PropertyHooks[]|null} hooks * @property {AttrGroup[]} attrGroups */ module.exports = Statement.extends( @@ -28,6 +29,7 @@ module.exports = Statement.extends( readonly, nullable, type, + hooks, attrGroups, docs, location, @@ -38,6 +40,7 @@ module.exports = Statement.extends( this.readonly = readonly; this.nullable = nullable; this.type = type; + this.hooks = hooks; this.attrGroups = attrGroups; }, ); diff --git a/src/ast/propertyhooks.js b/src/ast/propertyhooks.js new file mode 100644 index 000000000..40f6c98bf --- /dev/null +++ b/src/ast/propertyhooks.js @@ -0,0 +1,26 @@ +/** + * Copyright (C) 2024 Glayzzle (BSD3 License) + * @authors https://github.com/glayzzle/php-parser/graphs/contributors + * @url http://glayzzle.com + */ +"use strict"; + +const Statement = require("./statement"); +const KIND = "propertyhooks"; + +/** + * Defines a class property getter & setts + * @constructor PropertyHooks + * @memberOf module:php-parser + * @extends {Statement} + * @property {string} name + * @property {Block|Statement} body + */ +module.exports = Statement.extends( + KIND, + function PropertyHook(name, body, docs, location) { + Statement.apply(this, [KIND, docs, location]); + this.name = name; + this.body = body; + }, +); diff --git a/src/parser/class.js b/src/parser/class.js index ccb4e9414..0a1aa3d54 100644 --- a/src/parser/class.js +++ b/src/parser/class.js @@ -152,8 +152,6 @@ module.exports = { // reads a variable const variables = this.read_variable_list(flags, attrs); attrs = []; - this.expect(";"); - this.next(); result = result.concat(variables); } else { // raise an error @@ -178,7 +176,7 @@ module.exports = { * ``` */ read_variable_list: function (flags, attrs) { - const result = this.node("propertystatement"); + let property_statement = this.node("propertystatement"); const properties = this.read_list( /* @@ -203,19 +201,82 @@ module.exports = { propName = propName(name); let value = null; + let property_hooks = null; - this.expect([",", ";", "="]); + this.expect([",", ";", "=", "{"]); + + // Property has a value if (this.token === "=") { // https://github.com/php/php-src/blob/master/Zend/zend_language_parser.y#L815 value = this.next().read_expr(); } - return result(propName, value, readonly, nullable, type, attrs || []); + + // Property is using a hook to define getter/setters + else if (this.token === "{") { + this.next(); + property_hooks = this.read_property_hooks(); + } else { + this.expect([";", ","]); + } + + return result( + propName, + value, + readonly, + nullable, + type, + property_hooks, + attrs || [], + ); }, ",", ); - return result(null, properties, flags); + property_statement = property_statement(null, properties, flags); + + // semicolons are found only for regular properties definitions. + // Property hooks are terminated by a closing curly brace, }. + // property_statement is instanciated before this check to avoid including the semicolon in the AST end location of the property. + if (this.token === ";") { + this.next(); + } + return property_statement; }, + /** + * Reads a property hooks + * @returns {[null,null]} + */ + read_property_hooks: function () { + if (this.version < 804) { + this.raiseError("Parse Error: Typed Class Constants requires PHP 8.4+"); + } + + const hooks = this.read_list(function read_property_hook() { + const property_hooks = this.node("propertyhooks"); + const method_name = this.text(); + let body = null; + this.next(); + this.expect([this.tok.T_DOUBLE_ARROW, "{"]); + if (this.token === this.tok.T_DOUBLE_ARROW) { + this.next(); + body = this.read_expr(); + this.next(); + } else if (this.token === "{") { + body = this.read_code_block(); + // this.next(); + } + return property_hooks(method_name, body); + }, ","); + + // this.next(); + this.expect("}"); + if (this.token === "}") { + this.next(); + return hooks; + } + return null; + }, + /* * Reads constant list * ```ebnf diff --git a/test/snapshot/__snapshots__/acid.test.js.snap b/test/snapshot/__snapshots__/acid.test.js.snap index 66955f4bb..d9a778fa6 100644 --- a/test/snapshot/__snapshots__/acid.test.js.snap +++ b/test/snapshot/__snapshots__/acid.test.js.snap @@ -852,6 +852,7 @@ Program { "properties": [ Property { "attrGroups": [], + "hooks": null, "kind": "property", "loc": Location { "end": Position { diff --git a/test/snapshot/__snapshots__/attributes.test.js.snap b/test/snapshot/__snapshots__/attributes.test.js.snap index 36fb1d4f0..45f3a43ff 100644 --- a/test/snapshot/__snapshots__/attributes.test.js.snap +++ b/test/snapshot/__snapshots__/attributes.test.js.snap @@ -381,6 +381,7 @@ Program { "kind": "attrgroup", }, ], + "hooks": null, "kind": "property", "name": Identifier { "kind": "identifier", @@ -415,6 +416,7 @@ Program { "kind": "attrgroup", }, ], + "hooks": null, "kind": "property", "name": Identifier { "kind": "identifier", @@ -449,6 +451,7 @@ Program { "kind": "attrgroup", }, ], + "hooks": null, "kind": "property", "name": Identifier { "kind": "identifier", @@ -2652,6 +2655,7 @@ Program { "kind": "attrgroup", }, ], + "hooks": null, "kind": "property", "name": Identifier { "kind": "identifier", diff --git a/test/snapshot/__snapshots__/class.test.js.snap b/test/snapshot/__snapshots__/class.test.js.snap index d408ec7d5..d40b49ef3 100644 --- a/test/snapshot/__snapshots__/class.test.js.snap +++ b/test/snapshot/__snapshots__/class.test.js.snap @@ -66,6 +66,7 @@ Program { "properties": [ Property { "attrGroups": [], + "hooks": null, "kind": "property", "name": Identifier { "kind": "identifier", @@ -466,6 +467,7 @@ Program { "properties": [ Property { "attrGroups": [], + "hooks": null, "kind": "property", "name": Identifier { "kind": "identifier", @@ -514,6 +516,7 @@ Program { "properties": [ Property { "attrGroups": [], + "hooks": null, "kind": "property", "name": Identifier { "kind": "identifier", @@ -540,6 +543,7 @@ Program { "properties": [ Property { "attrGroups": [], + "hooks": null, "kind": "property", "name": Identifier { "kind": "identifier", @@ -599,6 +603,7 @@ Program { "properties": [ Property { "attrGroups": [], + "hooks": null, "kind": "property", "name": Identifier { "kind": "identifier", @@ -1158,163 +1163,7 @@ Program { } `; -exports[`Test classes Test that readonly method parameters are throwing errors 1`] = ` -Program { - "children": [ - Class { - "attrGroups": [], - "body": [ - Method { - "arguments": [ - Parameter { - "attrGroups": [], - "byref": false, - "flags": 1, - "kind": "parameter", - "name": null, - "nullable": false, - "readonly": false, - "type": null, - "value": null, - "variadic": false, - }, - ], - "attrGroups": [], - "body": null, - "byref": false, - "isAbstract": false, - "isFinal": false, - "isReadonly": false, - "isStatic": false, - "kind": "method", - "name": Identifier { - "kind": "identifier", - "name": "foo", - }, - "nullable": false, - "type": null, - "visibility": "public", - }, - PropertyStatement { - "isStatic": false, - "kind": "propertystatement", - "properties": [ - Property { - "attrGroups": [], - "kind": "property", - "name": Identifier { - "kind": "identifier", - "name": "id", - }, - "nullable": false, - "readonly": true, - "type": TypeReference { - "kind": "typereference", - "name": "int", - "raw": "int", - }, - "value": null, - }, - ], - "visibility": "", - }, - ], - "extends": null, - "implements": null, - "isAbstract": false, - "isAnonymous": false, - "isFinal": false, - "isReadonly": false, - "kind": "class", - "name": Identifier { - "kind": "identifier", - "name": "Bob", - }, - }, - ExpressionStatement { - "expression": undefined, - "kind": "expressionstatement", - }, - ], - "errors": [ - Error { - "expected": undefined, - "kind": "error", - "line": 3, - "message": "readonly properties can be used only on class constructor on line 3", - "token": undefined, - }, - Error { - "expected": 222, - "kind": "error", - "line": 3, - "message": "Parse Error : syntax error, unexpected 'readonly' (T_READ_ONLY), expecting T_VARIABLE on line 3", - "token": "'readonly' (T_READ_ONLY)", - }, - Error { - "expected": [ - ",", - ")", - ], - "kind": "error", - "line": 3, - "message": "Parse Error : syntax error, unexpected 'readonly' (T_READ_ONLY) on line 3", - "token": "'readonly' (T_READ_ONLY)", - }, - Error { - "expected": ")", - "kind": "error", - "line": 3, - "message": "Parse Error : syntax error, unexpected 'readonly' (T_READ_ONLY), expecting ')' on line 3", - "token": "'readonly' (T_READ_ONLY)", - }, - Error { - "expected": "{", - "kind": "error", - "line": 3, - "message": "Parse Error : syntax error, unexpected 'readonly' (T_READ_ONLY), expecting '{' on line 3", - "token": "'readonly' (T_READ_ONLY)", - }, - Error { - "expected": [ - ",", - ";", - "=", - ], - "kind": "error", - "line": 3, - "message": "Parse Error : syntax error, unexpected ')' on line 3", - "token": "')'", - }, - Error { - "expected": ";", - "kind": "error", - "line": 3, - "message": "Parse Error : syntax error, unexpected ')', expecting ';' on line 3", - "token": "')'", - }, - Error { - "expected": [ - 198, - 222, - 182, - ], - "kind": "error", - "line": 3, - "message": "Parse Error : syntax error, unexpected '{' on line 3", - "token": "'{'", - }, - Error { - "expected": "EXPR", - "kind": "error", - "line": 4, - "message": "Parse Error : syntax error, unexpected '}' on line 4", - "token": "'}'", - }, - ], - "kind": "program", -} -`; +exports[`Test classes Test that readonly method parameters are throwing errors 1`] = `"readonly properties can be used only on class constructor on line 3"`; exports[`Test classes Validate usual declarations 1`] = ` Program { @@ -1352,6 +1201,7 @@ Program { "properties": [ Property { "attrGroups": [], + "hooks": null, "kind": "property", "name": Identifier { "kind": "identifier", @@ -1527,6 +1377,7 @@ Program { "properties": [ Property { "attrGroups": [], + "hooks": null, "kind": "property", "name": Identifier { "kind": "identifier", @@ -1546,6 +1397,7 @@ Program { "properties": [ Property { "attrGroups": [], + "hooks": null, "kind": "property", "name": Identifier { "kind": "identifier", @@ -1922,6 +1774,7 @@ Program { "properties": [ Property { "attrGroups": [], + "hooks": null, "kind": "property", "name": Identifier { "kind": "identifier", diff --git a/test/snapshot/__snapshots__/classpropertyhooks.test.js.snap b/test/snapshot/__snapshots__/classpropertyhooks.test.js.snap new file mode 100644 index 000000000..604367ab4 --- /dev/null +++ b/test/snapshot/__snapshots__/classpropertyhooks.test.js.snap @@ -0,0 +1,165 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`classpropertyhooks getter arrow function 1`] = ` +Program { + "children": [ + Class { + "attrGroups": [], + "body": [ + PropertyStatement { + "isStatic": false, + "kind": "propertystatement", + "properties": [ + Property { + "attrGroups": [], + "hooks": [ + PropertyHook { + "body": Bin { + "kind": "bin", + "left": String { + "isDoubleQuote": false, + "kind": "string", + "raw": "'mailto:'", + "unicode": false, + "value": "mailto:", + }, + "right": PropertyLookup { + "kind": "propertylookup", + "offset": Identifier { + "kind": "identifier", + "name": "email", + }, + "what": Variable { + "curly": false, + "kind": "variable", + "name": "this", + }, + }, + "type": ".", + }, + "kind": "propertyhooks", + "name": "get", + }, + ], + "kind": "property", + "name": Identifier { + "kind": "identifier", + "name": "credits", + }, + "nullable": false, + "readonly": false, + "type": TypeReference { + "kind": "typereference", + "name": "string", + "raw": "string", + }, + "value": null, + }, + ], + "visibility": "public", + }, + ], + "extends": null, + "implements": null, + "isAbstract": false, + "isAnonymous": false, + "isFinal": false, + "isReadonly": false, + "kind": "class", + "name": Identifier { + "kind": "identifier", + "name": "BookViewModel", + }, + }, + ], + "errors": [], + "kind": "program", +} +`; + +exports[`classpropertyhooks getter block 1`] = ` +Program { + "children": [ + Class { + "attrGroups": [], + "body": [ + PropertyStatement { + "isStatic": false, + "kind": "propertystatement", + "properties": [ + Property { + "attrGroups": [], + "hooks": [ + PropertyHook { + "body": Block { + "children": [ + ExpressionStatement { + "expression": Bin { + "kind": "bin", + "left": String { + "isDoubleQuote": false, + "kind": "string", + "raw": "'mailto:'", + "unicode": false, + "value": "mailto:", + }, + "right": PropertyLookup { + "kind": "propertylookup", + "offset": Identifier { + "kind": "identifier", + "name": "email", + }, + "what": Variable { + "curly": false, + "kind": "variable", + "name": "this", + }, + }, + "type": ".", + }, + "kind": "expressionstatement", + }, + ], + "kind": "block", + }, + "kind": "propertyhooks", + "name": "get", + }, + ], + "kind": "property", + "name": Identifier { + "kind": "identifier", + "name": "credits", + }, + "nullable": false, + "readonly": false, + "type": TypeReference { + "kind": "typereference", + "name": "string", + "raw": "string", + }, + "value": null, + }, + ], + "visibility": "public", + }, + ], + "extends": null, + "implements": null, + "isAbstract": false, + "isAnonymous": false, + "isFinal": false, + "isReadonly": false, + "kind": "class", + "name": Identifier { + "kind": "identifier", + "name": "BookViewModel", + }, + }, + ], + "errors": [], + "kind": "program", +} +`; + +exports[`classpropertyhooks not supported in php < 8.4 1`] = `"Parse Error: Typed Class Constants requires PHP 8.4+ on line 5"`; diff --git a/test/snapshot/__snapshots__/comment.test.js.snap b/test/snapshot/__snapshots__/comment.test.js.snap index 0288dac0c..ad0eae3d3 100644 --- a/test/snapshot/__snapshots__/comment.test.js.snap +++ b/test/snapshot/__snapshots__/comment.test.js.snap @@ -1864,6 +1864,7 @@ Program { "properties": [ Property { "attrGroups": [], + "hooks": null, "kind": "property", "name": Identifier { "kind": "identifier", @@ -1876,6 +1877,7 @@ Program { }, Property { "attrGroups": [], + "hooks": null, "kind": "property", "name": Identifier { "kind": "identifier", @@ -1908,6 +1910,7 @@ Program { "properties": [ Property { "attrGroups": [], + "hooks": null, "kind": "property", "name": Identifier { "kind": "identifier", diff --git a/test/snapshot/__snapshots__/graceful.test.js.snap b/test/snapshot/__snapshots__/graceful.test.js.snap index d1bf9b28c..286f3aaad 100644 --- a/test/snapshot/__snapshots__/graceful.test.js.snap +++ b/test/snapshot/__snapshots__/graceful.test.js.snap @@ -301,6 +301,7 @@ Program { "properties": [ Property { "attrGroups": [], + "hooks": null, "kind": "property", "name": Identifier { "kind": "identifier", @@ -318,6 +319,30 @@ Program { ], "visibility": "", }, + PropertyStatement { + "isStatic": false, + "kind": "propertystatement", + "properties": [ + Property { + "attrGroups": [], + "hooks": null, + "kind": "property", + "name": Identifier { + "kind": "identifier", + "name": "", + }, + "nullable": false, + "readonly": false, + "type": Name { + "kind": "name", + "name": "A", + "resolution": "uqn", + }, + "value": null, + }, + ], + "visibility": "", + }, ], "extends": null, "implements": null, @@ -345,6 +370,7 @@ Program { ",", ";", "=", + "{", ], "kind": "error", "line": 1, @@ -352,22 +378,43 @@ Program { "token": "'A' (T_STRING)", }, Error { - "expected": ";", + "expected": [ + ";", + ",", + ], "kind": "error", "line": 1, - "message": "Parse Error : syntax error, unexpected 'A' (T_STRING), expecting ';' on line 1", + "message": "Parse Error : syntax error, unexpected 'A' (T_STRING) on line 1", "token": "'A' (T_STRING)", }, + Error { + "expected": 222, + "kind": "error", + "line": 1, + "message": "Parse Error : syntax error, unexpected '=', expecting T_VARIABLE on line 1", + "token": "'='", + }, Error { "expected": [ - 198, - 222, - 182, + ",", + ";", + "=", + "{", ], "kind": "error", "line": 1, - "message": "Parse Error : syntax error, unexpected '=' on line 1", - "token": "'='", + "message": "Parse Error : syntax error, unexpected '1' (T_LNUMBER) on line 1", + "token": "'1' (T_LNUMBER)", + }, + Error { + "expected": [ + ";", + ",", + ], + "kind": "error", + "line": 1, + "message": "Parse Error : syntax error, unexpected '1' (T_LNUMBER) on line 1", + "token": "'1' (T_LNUMBER)", }, Error { "expected": [ @@ -844,6 +891,7 @@ Program { "properties": [ Property { "attrGroups": [], + "hooks": null, "kind": "property", "name": Identifier { "kind": "identifier", @@ -861,6 +909,30 @@ Program { ], "visibility": "", }, + PropertyStatement { + "isStatic": false, + "kind": "propertystatement", + "properties": [ + Property { + "attrGroups": [], + "hooks": null, + "kind": "property", + "name": Identifier { + "kind": "identifier", + "name": "", + }, + "nullable": false, + "readonly": false, + "type": Name { + "kind": "name", + "name": "baz", + "resolution": "uqn", + }, + "value": null, + }, + ], + "visibility": "", + }, ], "kind": "trait", "name": Identifier { @@ -889,6 +961,7 @@ Program { ",", ";", "=", + "{", ], "kind": "error", "line": 1, @@ -896,22 +969,43 @@ Program { "token": "'baz' (T_STRING)", }, Error { - "expected": ";", + "expected": [ + ";", + ",", + ], "kind": "error", "line": 1, - "message": "Parse Error : syntax error, unexpected 'baz' (T_STRING), expecting ';' on line 1", + "message": "Parse Error : syntax error, unexpected 'baz' (T_STRING) on line 1", "token": "'baz' (T_STRING)", }, + Error { + "expected": 222, + "kind": "error", + "line": 1, + "message": "Parse Error : syntax error, unexpected '{', expecting T_VARIABLE on line 1", + "token": "'{'", + }, Error { "expected": [ - 198, - 222, - 182, + ",", + ";", + "=", + "{", ], "kind": "error", "line": 1, - "message": "Parse Error : syntax error, unexpected '{' on line 1", - "token": "'{'", + "message": "Parse Error : syntax error, unexpected '}' on line 1", + "token": "'}'", + }, + Error { + "expected": [ + ";", + ",", + ], + "kind": "error", + "line": 1, + "message": "Parse Error : syntax error, unexpected '}' on line 1", + "token": "'}'", }, ], "kind": "program", diff --git a/test/snapshot/__snapshots__/heredoc.test.js.snap b/test/snapshot/__snapshots__/heredoc.test.js.snap index cdf9be706..1b07aba0e 100644 --- a/test/snapshot/__snapshots__/heredoc.test.js.snap +++ b/test/snapshot/__snapshots__/heredoc.test.js.snap @@ -1721,6 +1721,7 @@ FOOBAR", "properties": [ Property { "attrGroups": [], + "hooks": null, "kind": "property", "name": Identifier { "kind": "identifier", diff --git a/test/snapshot/__snapshots__/namespace.test.js.snap b/test/snapshot/__snapshots__/namespace.test.js.snap index c59e4055c..7caa9b786 100644 --- a/test/snapshot/__snapshots__/namespace.test.js.snap +++ b/test/snapshot/__snapshots__/namespace.test.js.snap @@ -185,6 +185,7 @@ Program { "properties": [ Property { "attrGroups": [], + "hooks": null, "kind": "property", "loc": Location { "end": Position { @@ -722,6 +723,7 @@ Program { "properties": [ Property { "attrGroups": [], + "hooks": null, "kind": "property", "loc": Location { "end": Position { diff --git a/test/snapshot/__snapshots__/nowdoc.test.js.snap b/test/snapshot/__snapshots__/nowdoc.test.js.snap index 73ee4fa13..29b7dfdd4 100644 --- a/test/snapshot/__snapshots__/nowdoc.test.js.snap +++ b/test/snapshot/__snapshots__/nowdoc.test.js.snap @@ -228,6 +228,7 @@ FOOBAR", "properties": [ Property { "attrGroups": [], + "hooks": null, "kind": "property", "name": Identifier { "kind": "identifier", diff --git a/test/snapshot/__snapshots__/property.test.js.snap b/test/snapshot/__snapshots__/property.test.js.snap index 59f1edd5f..748b70878 100644 --- a/test/snapshot/__snapshots__/property.test.js.snap +++ b/test/snapshot/__snapshots__/property.test.js.snap @@ -12,6 +12,7 @@ Program { "properties": [ Property { "attrGroups": [], + "hooks": null, "kind": "property", "name": Identifier { "kind": "identifier", @@ -56,6 +57,7 @@ Program { "properties": [ Property { "attrGroups": [], + "hooks": null, "kind": "property", "name": Identifier { "kind": "identifier", @@ -103,6 +105,7 @@ Program { "properties": [ Property { "attrGroups": [], + "hooks": null, "kind": "property", "name": Identifier { "kind": "identifier", @@ -147,6 +150,7 @@ Program { "properties": [ Property { "attrGroups": [], + "hooks": null, "kind": "property", "name": Identifier { "kind": "identifier", @@ -194,6 +198,7 @@ Program { "properties": [ Property { "attrGroups": [], + "hooks": null, "kind": "property", "name": Identifier { "kind": "identifier", @@ -238,6 +243,7 @@ Program { "properties": [ Property { "attrGroups": [], + "hooks": null, "kind": "property", "name": Identifier { "kind": "identifier", @@ -282,6 +288,7 @@ Program { "properties": [ Property { "attrGroups": [], + "hooks": null, "kind": "property", "name": Identifier { "kind": "identifier", @@ -329,6 +336,7 @@ Program { "properties": [ Property { "attrGroups": [], + "hooks": null, "kind": "property", "name": Identifier { "kind": "identifier", @@ -376,6 +384,7 @@ Program { "properties": [ Property { "attrGroups": [], + "hooks": null, "kind": "property", "name": Identifier { "kind": "identifier", @@ -420,6 +429,7 @@ Program { "properties": [ Property { "attrGroups": [], + "hooks": null, "kind": "property", "name": Identifier { "kind": "identifier", @@ -467,6 +477,7 @@ Program { "properties": [ Property { "attrGroups": [], + "hooks": null, "kind": "property", "name": Identifier { "kind": "identifier", @@ -538,6 +549,7 @@ Program { "properties": [ Property { "attrGroups": [], + "hooks": null, "kind": "property", "name": Identifier { "kind": "identifier", @@ -599,6 +611,7 @@ Program { "properties": [ Property { "attrGroups": [], + "hooks": null, "kind": "property", "name": Identifier { "kind": "identifier", @@ -654,6 +667,7 @@ Program { "properties": [ Property { "attrGroups": [], + "hooks": null, "kind": "property", "name": Identifier { "kind": "identifier", @@ -702,6 +716,7 @@ Program { "properties": [ Property { "attrGroups": [], + "hooks": null, "kind": "property", "name": Identifier { "kind": "identifier", @@ -750,6 +765,7 @@ Program { "properties": [ Property { "attrGroups": [], + "hooks": null, "kind": "property", "name": Identifier { "kind": "identifier", @@ -800,6 +816,7 @@ Program { "properties": [ Property { "attrGroups": [], + "hooks": null, "kind": "property", "name": Identifier { "kind": "identifier", @@ -866,6 +883,7 @@ Program { "properties": [ Property { "attrGroups": [], + "hooks": null, "kind": "property", "name": Identifier { "kind": "identifier", @@ -917,6 +935,7 @@ Program { "properties": [ Property { "attrGroups": [], + "hooks": null, "kind": "property", "name": Identifier { "kind": "identifier", @@ -988,6 +1007,7 @@ Program { "properties": [ Property { "attrGroups": [], + "hooks": null, "kind": "property", "name": Identifier { "kind": "identifier", @@ -1038,6 +1058,7 @@ Program { "properties": [ Property { "attrGroups": [], + "hooks": null, "kind": "property", "name": Identifier { "kind": "identifier", @@ -1085,6 +1106,7 @@ Program { "properties": [ Property { "attrGroups": [], + "hooks": null, "kind": "property", "name": Identifier { "kind": "identifier", diff --git a/test/snapshot/__snapshots__/propertystatement.test.js.snap b/test/snapshot/__snapshots__/propertystatement.test.js.snap index 3e3d14d9c..585066186 100644 --- a/test/snapshot/__snapshots__/propertystatement.test.js.snap +++ b/test/snapshot/__snapshots__/propertystatement.test.js.snap @@ -12,6 +12,7 @@ Program { "properties": [ Property { "attrGroups": [], + "hooks": null, "kind": "property", "name": Identifier { "kind": "identifier", @@ -24,6 +25,7 @@ Program { }, Property { "attrGroups": [], + "hooks": null, "kind": "property", "name": Identifier { "kind": "identifier", @@ -36,6 +38,7 @@ Program { }, Property { "attrGroups": [], + "hooks": null, "kind": "property", "name": Identifier { "kind": "identifier", @@ -80,6 +83,7 @@ Program { "properties": [ Property { "attrGroups": [], + "hooks": null, "kind": "property", "name": Identifier { "kind": "identifier", @@ -92,6 +96,7 @@ Program { }, Property { "attrGroups": [], + "hooks": null, "kind": "property", "name": Identifier { "kind": "identifier", @@ -104,6 +109,7 @@ Program { }, Property { "attrGroups": [], + "hooks": null, "kind": "property", "name": Identifier { "kind": "identifier", @@ -148,6 +154,7 @@ Program { "properties": [ Property { "attrGroups": [], + "hooks": null, "kind": "property", "name": Identifier { "kind": "identifier", @@ -192,6 +199,7 @@ Program { "properties": [ Property { "attrGroups": [], + "hooks": null, "kind": "property", "name": Identifier { "kind": "identifier", diff --git a/test/snapshot/class.test.js b/test/snapshot/class.test.js index b93d642cb..ba597ba52 100644 --- a/test/snapshot/class.test.js +++ b/test/snapshot/class.test.js @@ -182,19 +182,19 @@ describe("Test classes", function () { }); it("Test that readonly method parameters are throwing errors", function () { - const ast = parser.parseEval( - ` + expect(() => { + parser.parseEval( + ` class Bob { public function foo(public readonly int $id) {} }`, - { - parser: { - version: "8.1", - suppressErrors: true, + { + parser: { + version: "8.1", + }, }, - }, - ); - expect(ast).toMatchSnapshot(); + ); + }).toThrowErrorMatchingSnapshot(); }); it("Test promoted nullable properties php 8", function () { diff --git a/test/snapshot/classpropertyhooks.test.js b/test/snapshot/classpropertyhooks.test.js new file mode 100644 index 000000000..2c9aece78 --- /dev/null +++ b/test/snapshot/classpropertyhooks.test.js @@ -0,0 +1,65 @@ +const parser = require("../main"); +// +describe("classpropertyhooks", () => { + it("not supported in php < 8.4", () => { + expect(() => { + parser.parseEval( + ` + class BookViewModel + { + public string $credits { + get => 'mailto:' . $this->email; + } + } + `, + { + parser: { + version: "8.3", + }, + }, + ); + }).toThrowErrorMatchingSnapshot(); + }); + + it("getter arrow function", () => { + expect( + parser.parseEval( + ` + class BookViewModel + { + public string $credits { + get => 'mailto:' . $this->email; + } + } + `, + { + parser: { + version: "8.4", + }, + }, + ), + ).toMatchSnapshot(); + }); + + it("getter block", () => { + expect( + parser.parseEval( + ` + class BookViewModel + { + public string $credits { + get { + 'mailto:' . $this->email; + } + } + } + `, + { + parser: { + version: "8.4", + }, + }, + ), + ).toMatchSnapshot(); + }); +}); From 4b94c202801360734922d3ceb1a741dcd738b9f5 Mon Sep 17 00:00:00 2001 From: Thomas Genin Date: Sun, 1 Dec 2024 13:20:19 -0800 Subject: [PATCH 05/17] move getter into dedicated method --- src/parser/class.js | 33 ++++++++++----- .../classpropertyhooks.test.js.snap | 2 +- test/snapshot/classpropertyhooks.test.js | 41 +++++++------------ 3 files changed, 37 insertions(+), 39 deletions(-) diff --git a/src/parser/class.js b/src/parser/class.js index 0a1aa3d54..a5604bc5c 100644 --- a/src/parser/class.js +++ b/src/parser/class.js @@ -254,21 +254,19 @@ module.exports = { const hooks = this.read_list(function read_property_hook() { const property_hooks = this.node("propertyhooks"); const method_name = this.text(); - let body = null; - this.next(); - this.expect([this.tok.T_DOUBLE_ARROW, "{"]); - if (this.token === this.tok.T_DOUBLE_ARROW) { - this.next(); - body = this.read_expr(); - this.next(); - } else if (this.token === "{") { - body = this.read_code_block(); - // this.next(); + if (method_name !== "get" && method_name !== "set") { + this.raiseError( + "Parse Error: Property hooks must be either 'get' or 'set'", + ); } + this.next(); + const body = + method_name === "get" ? this.read_property_hook_getter() : null; + + // this.next(); return property_hooks(method_name, body); }, ","); - // this.next(); this.expect("}"); if (this.token === "}") { this.next(); @@ -277,6 +275,19 @@ module.exports = { return null; }, + read_property_hook_getter: function () { + let body = null; + this.expect([this.tok.T_DOUBLE_ARROW, "{"]); + if (this.token === this.tok.T_DOUBLE_ARROW) { + this.next(); + body = this.read_expr(); + this.next(); + } else if (this.token === "{") { + body = this.read_code_block(); + } + return body; + }, + /* * Reads constant list * ```ebnf diff --git a/test/snapshot/__snapshots__/classpropertyhooks.test.js.snap b/test/snapshot/__snapshots__/classpropertyhooks.test.js.snap index 604367ab4..eb45f4fa6 100644 --- a/test/snapshot/__snapshots__/classpropertyhooks.test.js.snap +++ b/test/snapshot/__snapshots__/classpropertyhooks.test.js.snap @@ -162,4 +162,4 @@ Program { } `; -exports[`classpropertyhooks not supported in php < 8.4 1`] = `"Parse Error: Typed Class Constants requires PHP 8.4+ on line 5"`; +exports[`classpropertyhooks not supported in php < 8.4 1`] = `"Parse Error: Typed Class Constants requires PHP 8.4+ on line 3"`; diff --git a/test/snapshot/classpropertyhooks.test.js b/test/snapshot/classpropertyhooks.test.js index 2c9aece78..1113d0839 100644 --- a/test/snapshot/classpropertyhooks.test.js +++ b/test/snapshot/classpropertyhooks.test.js @@ -1,17 +1,20 @@ const parser = require("../main"); // describe("classpropertyhooks", () => { + const test_parser = parser.create({ + parser: { + version: "8.4", + }, + }); + it("not supported in php < 8.4", () => { expect(() => { parser.parseEval( - ` - class BookViewModel - { + `class BookViewModel { public string $credits { get => 'mailto:' . $this->email; } - } - `, + }`, { parser: { version: "8.3", @@ -23,42 +26,26 @@ describe("classpropertyhooks", () => { it("getter arrow function", () => { expect( - parser.parseEval( - ` - class BookViewModel - { + test_parser.parseEval( + `class BookViewModel { public string $credits { get => 'mailto:' . $this->email; } - } - `, - { - parser: { - version: "8.4", - }, - }, + }`, ), ).toMatchSnapshot(); }); it("getter block", () => { expect( - parser.parseEval( - ` - class BookViewModel - { + test_parser.parseEval( + `class BookViewModel { public string $credits { get { 'mailto:' . $this->email; } } - } - `, - { - parser: { - version: "8.4", - }, - }, + }`, ), ).toMatchSnapshot(); }); From 8bb206f5c821d31354ab7988d52925d6b7674901 Mon Sep 17 00:00:00 2001 From: Thomas Genin Date: Sun, 1 Dec 2024 13:29:15 -0800 Subject: [PATCH 06/17] setter 1 --- src/ast.js | 4 +- src/ast/property.js | 2 +- src/ast/{propertyhooks.js => propertyhook.js} | 4 +- src/parser/class.js | 18 ++++- .../classpropertyhooks.test.js.snap | 79 ++++++++++++++++++- test/snapshot/classpropertyhooks.test.js | 42 +++++++--- 6 files changed, 127 insertions(+), 22 deletions(-) rename src/ast/{propertyhooks.js => propertyhook.js} (90%) diff --git a/src/ast.js b/src/ast.js index 4d9d0426d..5bed2679c 100644 --- a/src/ast.js +++ b/src/ast.js @@ -106,7 +106,7 @@ const Position = require("./ast/position"); * - [Namespace](#namespace) * - [PropertyStatement](#propertystatement) * - [Property](#property) - * - [PropertyHooks](#propertyhooks) + * - [PropertyHook](#propertyhook) * - [Declaration](#declaration) * - [Class](#class) * - [Interface](#interface) @@ -553,7 +553,7 @@ AST.prototype.checkNodes = function () { require("./ast/print"), require("./ast/program"), require("./ast/property"), - require("./ast/propertyhooks"), + require("./ast/propertyhook"), require("./ast/propertylookup"), require("./ast/propertystatement"), require("./ast/reference"), diff --git a/src/ast/property.js b/src/ast/property.js index cdb805c7d..afcc4e42d 100644 --- a/src/ast/property.js +++ b/src/ast/property.js @@ -18,7 +18,7 @@ const KIND = "property"; * @property {boolean} readonly * @property {boolean} nullable * @property {Identifier|Array|null} type - * @propert {PropertyHooks[]|null} hooks + * @propert {PropertyHook[]|null} hooks * @property {AttrGroup[]} attrGroups */ module.exports = Statement.extends( diff --git a/src/ast/propertyhooks.js b/src/ast/propertyhook.js similarity index 90% rename from src/ast/propertyhooks.js rename to src/ast/propertyhook.js index 40f6c98bf..7961c63b1 100644 --- a/src/ast/propertyhooks.js +++ b/src/ast/propertyhook.js @@ -6,11 +6,11 @@ "use strict"; const Statement = require("./statement"); -const KIND = "propertyhooks"; +const KIND = "propertyhook"; /** * Defines a class property getter & setts - * @constructor PropertyHooks + * @constructor PropertyHook * @memberOf module:php-parser * @extends {Statement} * @property {string} name diff --git a/src/parser/class.js b/src/parser/class.js index a5604bc5c..63d732694 100644 --- a/src/parser/class.js +++ b/src/parser/class.js @@ -252,7 +252,7 @@ module.exports = { } const hooks = this.read_list(function read_property_hook() { - const property_hooks = this.node("propertyhooks"); + const property_hooks = this.node("propertyhook"); const method_name = this.text(); if (method_name !== "get" && method_name !== "set") { this.raiseError( @@ -261,7 +261,9 @@ module.exports = { } this.next(); const body = - method_name === "get" ? this.read_property_hook_getter() : null; + method_name === "get" + ? this.read_property_hook_getter() + : this.read_property_hook_setter(); // this.next(); return property_hooks(method_name, body); @@ -288,6 +290,18 @@ module.exports = { return body; }, + read_property_hook_setter: function () { + let body = null; + this.expect([this.tok.T_DOUBLE_ARROW, "{"]); + if (this.token === this.tok.T_DOUBLE_ARROW) { + this.next(); + body = this.read_expr(); + this.next(); + } + + return body; + }, + /* * Reads constant list * ```ebnf diff --git a/test/snapshot/__snapshots__/classpropertyhooks.test.js.snap b/test/snapshot/__snapshots__/classpropertyhooks.test.js.snap index eb45f4fa6..952d12856 100644 --- a/test/snapshot/__snapshots__/classpropertyhooks.test.js.snap +++ b/test/snapshot/__snapshots__/classpropertyhooks.test.js.snap @@ -37,7 +37,7 @@ Program { }, "type": ".", }, - "kind": "propertyhooks", + "kind": "propertyhook", "name": "get", }, ], @@ -122,7 +122,7 @@ Program { ], "kind": "block", }, - "kind": "propertyhooks", + "kind": "propertyhook", "name": "get", }, ], @@ -163,3 +163,78 @@ Program { `; exports[`classpropertyhooks not supported in php < 8.4 1`] = `"Parse Error: Typed Class Constants requires PHP 8.4+ on line 3"`; + +exports[`classpropertyhooks setter expression form with implicit $value 1`] = ` +Program { + "children": [ + Class { + "attrGroups": [], + "body": [ + PropertyStatement { + "isStatic": false, + "kind": "propertystatement", + "properties": [ + Property { + "attrGroups": [], + "hooks": [ + PropertyHook { + "body": Assign { + "kind": "assign", + "left": PropertyLookup { + "kind": "propertylookup", + "offset": Identifier { + "kind": "identifier", + "name": "credits", + }, + "what": Variable { + "curly": false, + "kind": "variable", + "name": "this", + }, + }, + "operator": "=", + "right": Variable { + "curly": false, + "kind": "variable", + "name": "value", + }, + }, + "kind": "propertyhook", + "name": "set", + }, + ], + "kind": "property", + "name": Identifier { + "kind": "identifier", + "name": "credits", + }, + "nullable": false, + "readonly": false, + "type": TypeReference { + "kind": "typereference", + "name": "string", + "raw": "string", + }, + "value": null, + }, + ], + "visibility": "public", + }, + ], + "extends": null, + "implements": null, + "isAbstract": false, + "isAnonymous": false, + "isFinal": false, + "isReadonly": false, + "kind": "class", + "name": Identifier { + "kind": "identifier", + "name": "BookViewModel", + }, + }, + ], + "errors": [], + "kind": "program", +} +`; diff --git a/test/snapshot/classpropertyhooks.test.js b/test/snapshot/classpropertyhooks.test.js index 1113d0839..e76f85d4d 100644 --- a/test/snapshot/classpropertyhooks.test.js +++ b/test/snapshot/classpropertyhooks.test.js @@ -24,29 +24,45 @@ describe("classpropertyhooks", () => { }).toThrowErrorMatchingSnapshot(); }); - it("getter arrow function", () => { - expect( - test_parser.parseEval( - `class BookViewModel { + describe("getter", () => { + it("arrow function", () => { + expect( + test_parser.parseEval( + `class BookViewModel { public string $credits { get => 'mailto:' . $this->email; } }`, - ), - ).toMatchSnapshot(); - }); + ), + ).toMatchSnapshot(); + }); - it("getter block", () => { - expect( - test_parser.parseEval( - `class BookViewModel { + it("block", () => { + expect( + test_parser.parseEval( + `class BookViewModel { public string $credits { get { 'mailto:' . $this->email; } } }`, - ), - ).toMatchSnapshot(); + ), + ).toMatchSnapshot(); + }); + }); + + describe("setter", () => { + it("expression form with implicit $value", () => { + expect( + test_parser.parseEval( + `class BookViewModel { + public string $credits { + set => $this->credits = $value; + } + }`, + ), + ).toMatchSnapshot(); + }); }); }); From e5163cf73680abb85d30d0e089836f8c937676cb Mon Sep 17 00:00:00 2001 From: Thomas Genin Date: Sun, 1 Dec 2024 13:32:43 -0800 Subject: [PATCH 07/17] setter 2 --- src/parser/class.js | 2 + .../classpropertyhooks.test.js.snap | 124 ++++++++++++++++++ test/snapshot/classpropertyhooks.test.js | 15 +++ 3 files changed, 141 insertions(+) diff --git a/src/parser/class.js b/src/parser/class.js index 63d732694..75fae57de 100644 --- a/src/parser/class.js +++ b/src/parser/class.js @@ -297,6 +297,8 @@ module.exports = { this.next(); body = this.read_expr(); this.next(); + } else if (this.token === "{") { + body = this.read_code_block(); } return body; diff --git a/test/snapshot/__snapshots__/classpropertyhooks.test.js.snap b/test/snapshot/__snapshots__/classpropertyhooks.test.js.snap index 952d12856..1c0a1a617 100644 --- a/test/snapshot/__snapshots__/classpropertyhooks.test.js.snap +++ b/test/snapshot/__snapshots__/classpropertyhooks.test.js.snap @@ -164,6 +164,130 @@ Program { exports[`classpropertyhooks not supported in php < 8.4 1`] = `"Parse Error: Typed Class Constants requires PHP 8.4+ on line 3"`; +exports[`classpropertyhooks setter block form with implicit $value 1`] = ` +Program { + "children": [ + Class { + "attrGroups": [], + "body": [ + PropertyStatement { + "isStatic": false, + "kind": "propertystatement", + "properties": [ + Property { + "attrGroups": [], + "hooks": [ + PropertyHook { + "body": Block { + "children": [ + ExpressionStatement { + "expression": Assign { + "kind": "assign", + "left": Variable { + "curly": false, + "kind": "variable", + "name": "tmp", + }, + "operator": "=", + "right": Bin { + "kind": "bin", + "left": PropertyLookup { + "kind": "propertylookup", + "offset": Identifier { + "kind": "identifier", + "name": "credits", + }, + "what": Variable { + "curly": false, + "kind": "variable", + "name": "this", + }, + }, + "right": Number { + "kind": "number", + "value": "1", + }, + "type": "+", + }, + }, + "kind": "expressionstatement", + }, + ExpressionStatement { + "expression": Assign { + "kind": "assign", + "left": PropertyLookup { + "kind": "propertylookup", + "offset": Identifier { + "kind": "identifier", + "name": "propertyName", + }, + "what": Variable { + "curly": false, + "kind": "variable", + "name": "this", + }, + }, + "operator": "=", + "right": Bin { + "kind": "bin", + "left": Variable { + "curly": false, + "kind": "variable", + "name": "value", + }, + "right": Variable { + "curly": false, + "kind": "variable", + "name": "tmp", + }, + "type": "+", + }, + }, + "kind": "expressionstatement", + }, + ], + "kind": "block", + }, + "kind": "propertyhook", + "name": "set", + }, + ], + "kind": "property", + "name": Identifier { + "kind": "identifier", + "name": "credits", + }, + "nullable": false, + "readonly": false, + "type": TypeReference { + "kind": "typereference", + "name": "string", + "raw": "string", + }, + "value": null, + }, + ], + "visibility": "public", + }, + ], + "extends": null, + "implements": null, + "isAbstract": false, + "isAnonymous": false, + "isFinal": false, + "isReadonly": false, + "kind": "class", + "name": Identifier { + "kind": "identifier", + "name": "BookViewModel", + }, + }, + ], + "errors": [], + "kind": "program", +} +`; + exports[`classpropertyhooks setter expression form with implicit $value 1`] = ` Program { "children": [ diff --git a/test/snapshot/classpropertyhooks.test.js b/test/snapshot/classpropertyhooks.test.js index e76f85d4d..57033d584 100644 --- a/test/snapshot/classpropertyhooks.test.js +++ b/test/snapshot/classpropertyhooks.test.js @@ -64,5 +64,20 @@ describe("classpropertyhooks", () => { ), ).toMatchSnapshot(); }); + + it("block form with implicit $value", () => { + expect( + test_parser.parseEval( + `class BookViewModel { + public string $credits { + set { + $tmp = $this->credits + 1; + $this->propertyName = $value + $tmp; + } + } + }`, + ), + ).toMatchSnapshot(); + }); }); }); From f165e3b1d09d9a025205fdab9352a0d830ddbcb2 Mon Sep 17 00:00:00 2001 From: Thomas Genin Date: Sun, 1 Dec 2024 14:02:10 -0800 Subject: [PATCH 08/17] setter 3 --- src/ast/propertyhook.js | 7 +- src/parser/class.js | 54 +- .../classpropertyhooks.test.js.snap | 482 ++++++++++++++++++ test/snapshot/classpropertyhooks.test.js | 82 ++- 4 files changed, 577 insertions(+), 48 deletions(-) diff --git a/src/ast/propertyhook.js b/src/ast/propertyhook.js index 7961c63b1..e3a831492 100644 --- a/src/ast/propertyhook.js +++ b/src/ast/propertyhook.js @@ -9,18 +9,21 @@ const Statement = require("./statement"); const KIND = "propertyhook"; /** - * Defines a class property getter & setts + * Defines a class property hook getter & setter + * * @constructor PropertyHook * @memberOf module:php-parser * @extends {Statement} * @property {string} name + * @property {Parameter|null} parameter * @property {Block|Statement} body */ module.exports = Statement.extends( KIND, - function PropertyHook(name, body, docs, location) { + function PropertyHook(name, parameter, body, docs, location) { Statement.apply(this, [KIND, docs, location]); this.name = name; + this.parameter = parameter; this.body = body; }, ); diff --git a/src/parser/class.js b/src/parser/class.js index 75fae57de..672837a54 100644 --- a/src/parser/class.js +++ b/src/parser/class.js @@ -260,16 +260,29 @@ module.exports = { ); } this.next(); - const body = - method_name === "get" - ? this.read_property_hook_getter() - : this.read_property_hook_setter(); - // this.next(); - return property_hooks(method_name, body); + let parameter = null; + let body = null; + this.expect([this.tok.T_DOUBLE_ARROW, "{", "("]); + + if (this.token === "(") { + this.next(); + parameter = this.read_parameter(false); + this.expect(")"); + this.next(); + } + + if (this.token === this.tok.T_DOUBLE_ARROW) { + this.next(); + body = this.read_expr(); + this.next(); + } else if (this.token === "{") { + body = this.read_code_block(); + } + + return property_hooks(method_name, parameter, body); }, ","); - this.expect("}"); if (this.token === "}") { this.next(); return hooks; @@ -277,33 +290,6 @@ module.exports = { return null; }, - read_property_hook_getter: function () { - let body = null; - this.expect([this.tok.T_DOUBLE_ARROW, "{"]); - if (this.token === this.tok.T_DOUBLE_ARROW) { - this.next(); - body = this.read_expr(); - this.next(); - } else if (this.token === "{") { - body = this.read_code_block(); - } - return body; - }, - - read_property_hook_setter: function () { - let body = null; - this.expect([this.tok.T_DOUBLE_ARROW, "{"]); - if (this.token === this.tok.T_DOUBLE_ARROW) { - this.next(); - body = this.read_expr(); - this.next(); - } else if (this.token === "{") { - body = this.read_code_block(); - } - - return body; - }, - /* * Reads constant list * ```ebnf diff --git a/test/snapshot/__snapshots__/classpropertyhooks.test.js.snap b/test/snapshot/__snapshots__/classpropertyhooks.test.js.snap index 1c0a1a617..c20161e5f 100644 --- a/test/snapshot/__snapshots__/classpropertyhooks.test.js.snap +++ b/test/snapshot/__snapshots__/classpropertyhooks.test.js.snap @@ -39,6 +39,7 @@ Program { }, "kind": "propertyhook", "name": "get", + "parameter": null, }, ], "kind": "property", @@ -124,6 +125,7 @@ Program { }, "kind": "propertyhook", "name": "get", + "parameter": null, }, ], "kind": "property", @@ -164,6 +166,284 @@ Program { exports[`classpropertyhooks not supported in php < 8.4 1`] = `"Parse Error: Typed Class Constants requires PHP 8.4+ on line 3"`; +exports[`classpropertyhooks setter block form with explicit typed $value 1`] = ` +Program { + "children": [ + Class { + "attrGroups": [], + "body": [ + PropertyStatement { + "isStatic": false, + "kind": "propertystatement", + "properties": [ + Property { + "attrGroups": [], + "hooks": [ + PropertyHook { + "body": Block { + "children": [ + ExpressionStatement { + "expression": Assign { + "kind": "assign", + "left": Variable { + "curly": false, + "kind": "variable", + "name": "tmp", + }, + "operator": "=", + "right": Bin { + "kind": "bin", + "left": PropertyLookup { + "kind": "propertylookup", + "offset": Identifier { + "kind": "identifier", + "name": "credits", + }, + "what": Variable { + "curly": false, + "kind": "variable", + "name": "this", + }, + }, + "right": Number { + "kind": "number", + "value": "1", + }, + "type": "+", + }, + }, + "kind": "expressionstatement", + }, + ExpressionStatement { + "expression": Assign { + "kind": "assign", + "left": PropertyLookup { + "kind": "propertylookup", + "offset": Identifier { + "kind": "identifier", + "name": "propertyName", + }, + "what": Variable { + "curly": false, + "kind": "variable", + "name": "this", + }, + }, + "operator": "=", + "right": Bin { + "kind": "bin", + "left": Variable { + "curly": false, + "kind": "variable", + "name": "value", + }, + "right": Variable { + "curly": false, + "kind": "variable", + "name": "tmp", + }, + "type": "+", + }, + }, + "kind": "expressionstatement", + }, + ], + "kind": "block", + }, + "kind": "propertyhook", + "name": "set", + "parameter": Parameter { + "attrGroups": [], + "byref": false, + "flags": 0, + "kind": "parameter", + "name": Identifier { + "kind": "identifier", + "name": "value", + }, + "nullable": false, + "readonly": false, + "type": TypeReference { + "kind": "typereference", + "name": "int", + "raw": "int", + }, + "value": null, + "variadic": false, + }, + }, + ], + "kind": "property", + "name": Identifier { + "kind": "identifier", + "name": "credits", + }, + "nullable": false, + "readonly": false, + "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": "BookViewModel", + }, + }, + ], + "errors": [], + "kind": "program", +} +`; + +exports[`classpropertyhooks setter block form with explicit untyped $value 1`] = ` +Program { + "children": [ + Class { + "attrGroups": [], + "body": [ + PropertyStatement { + "isStatic": false, + "kind": "propertystatement", + "properties": [ + Property { + "attrGroups": [], + "hooks": [ + PropertyHook { + "body": Block { + "children": [ + ExpressionStatement { + "expression": Assign { + "kind": "assign", + "left": Variable { + "curly": false, + "kind": "variable", + "name": "tmp", + }, + "operator": "=", + "right": Bin { + "kind": "bin", + "left": PropertyLookup { + "kind": "propertylookup", + "offset": Identifier { + "kind": "identifier", + "name": "credits", + }, + "what": Variable { + "curly": false, + "kind": "variable", + "name": "this", + }, + }, + "right": Number { + "kind": "number", + "value": "1", + }, + "type": "+", + }, + }, + "kind": "expressionstatement", + }, + ExpressionStatement { + "expression": Assign { + "kind": "assign", + "left": PropertyLookup { + "kind": "propertylookup", + "offset": Identifier { + "kind": "identifier", + "name": "propertyName", + }, + "what": Variable { + "curly": false, + "kind": "variable", + "name": "this", + }, + }, + "operator": "=", + "right": Bin { + "kind": "bin", + "left": Variable { + "curly": false, + "kind": "variable", + "name": "value", + }, + "right": Variable { + "curly": false, + "kind": "variable", + "name": "tmp", + }, + "type": "+", + }, + }, + "kind": "expressionstatement", + }, + ], + "kind": "block", + }, + "kind": "propertyhook", + "name": "set", + "parameter": Parameter { + "attrGroups": [], + "byref": false, + "flags": 0, + "kind": "parameter", + "name": Identifier { + "kind": "identifier", + "name": "value", + }, + "nullable": false, + "readonly": false, + "type": null, + "value": null, + "variadic": false, + }, + }, + ], + "kind": "property", + "name": Identifier { + "kind": "identifier", + "name": "credits", + }, + "nullable": false, + "readonly": false, + "type": null, + "value": null, + }, + ], + "visibility": "public", + }, + ], + "extends": null, + "implements": null, + "isAbstract": false, + "isAnonymous": false, + "isFinal": false, + "isReadonly": false, + "kind": "class", + "name": Identifier { + "kind": "identifier", + "name": "BookViewModel", + }, + }, + ], + "errors": [], + "kind": "program", +} +`; + exports[`classpropertyhooks setter block form with implicit $value 1`] = ` Program { "children": [ @@ -250,6 +530,7 @@ Program { }, "kind": "propertyhook", "name": "set", + "parameter": null, }, ], "kind": "property", @@ -288,6 +569,206 @@ Program { } `; +exports[`classpropertyhooks setter expression form with explicit typed $value 1`] = ` +Program { + "children": [ + Class { + "attrGroups": [], + "body": [ + PropertyStatement { + "isStatic": false, + "kind": "propertystatement", + "properties": [ + Property { + "attrGroups": [], + "hooks": [ + PropertyHook { + "body": Assign { + "kind": "assign", + "left": PropertyLookup { + "kind": "propertylookup", + "offset": Identifier { + "kind": "identifier", + "name": "name", + }, + "what": Variable { + "curly": false, + "kind": "variable", + "name": "this", + }, + }, + "operator": "=", + "right": Call { + "arguments": [ + Variable { + "curly": false, + "kind": "variable", + "name": "new_name", + }, + ], + "kind": "call", + "what": Name { + "kind": "name", + "name": "strtolower", + "resolution": "uqn", + }, + }, + }, + "kind": "propertyhook", + "name": "set", + "parameter": Parameter { + "attrGroups": [], + "byref": false, + "flags": 0, + "kind": "parameter", + "name": Identifier { + "kind": "identifier", + "name": "new_name", + }, + "nullable": false, + "readonly": false, + "type": TypeReference { + "kind": "typereference", + "name": "string", + "raw": "string", + }, + "value": null, + "variadic": false, + }, + }, + ], + "kind": "property", + "name": Identifier { + "kind": "identifier", + "name": "name", + }, + "nullable": false, + "readonly": false, + "type": TypeReference { + "kind": "typereference", + "name": "string", + "raw": "string", + }, + "value": null, + }, + ], + "visibility": "public", + }, + ], + "extends": null, + "implements": null, + "isAbstract": false, + "isAnonymous": false, + "isFinal": false, + "isReadonly": false, + "kind": "class", + "name": Identifier { + "kind": "identifier", + "name": "Person", + }, + }, + ], + "errors": [], + "kind": "program", +} +`; + +exports[`classpropertyhooks setter expression form with explicit untyped $value 1`] = ` +Program { + "children": [ + Class { + "attrGroups": [], + "body": [ + PropertyStatement { + "isStatic": false, + "kind": "propertystatement", + "properties": [ + Property { + "attrGroups": [], + "hooks": [ + PropertyHook { + "body": Assign { + "kind": "assign", + "left": PropertyLookup { + "kind": "propertylookup", + "offset": Identifier { + "kind": "identifier", + "name": "name", + }, + "what": Variable { + "curly": false, + "kind": "variable", + "name": "this", + }, + }, + "operator": "=", + "right": Call { + "arguments": [ + Variable { + "curly": false, + "kind": "variable", + "name": "new_name", + }, + ], + "kind": "call", + "what": Name { + "kind": "name", + "name": "strtolower", + "resolution": "uqn", + }, + }, + }, + "kind": "propertyhook", + "name": "set", + "parameter": Parameter { + "attrGroups": [], + "byref": false, + "flags": 0, + "kind": "parameter", + "name": Identifier { + "kind": "identifier", + "name": "new_name", + }, + "nullable": false, + "readonly": false, + "type": null, + "value": null, + "variadic": false, + }, + }, + ], + "kind": "property", + "name": Identifier { + "kind": "identifier", + "name": "name", + }, + "nullable": false, + "readonly": false, + "type": null, + "value": null, + }, + ], + "visibility": "public", + }, + ], + "extends": null, + "implements": null, + "isAbstract": false, + "isAnonymous": false, + "isFinal": false, + "isReadonly": false, + "kind": "class", + "name": Identifier { + "kind": "identifier", + "name": "Person", + }, + }, + ], + "errors": [], + "kind": "program", +} +`; + exports[`classpropertyhooks setter expression form with implicit $value 1`] = ` Program { "children": [ @@ -325,6 +806,7 @@ Program { }, "kind": "propertyhook", "name": "set", + "parameter": null, }, ], "kind": "property", diff --git a/test/snapshot/classpropertyhooks.test.js b/test/snapshot/classpropertyhooks.test.js index 57033d584..8d85c4fb3 100644 --- a/test/snapshot/classpropertyhooks.test.js +++ b/test/snapshot/classpropertyhooks.test.js @@ -53,22 +53,49 @@ describe("classpropertyhooks", () => { }); describe("setter", () => { - it("expression form with implicit $value", () => { - expect( - test_parser.parseEval( - `class BookViewModel { + describe("expression form", () => { + it("with implicit $value", () => { + expect( + test_parser.parseEval( + `class BookViewModel { public string $credits { set => $this->credits = $value; } }`, - ), - ).toMatchSnapshot(); + ), + ).toMatchSnapshot(); + }); + + it("with explicit untyped $value", () => { + expect( + test_parser.parseEval( + `class Person { + public $name { + set($new_name) => $this->name = strtolower($new_name); + } + }`, + ), + ).toMatchSnapshot(); + }); + + it("with explicit typed $value", () => { + expect( + test_parser.parseEval( + `class Person { + public string $name { + set(string $new_name) => $this->name = strtolower($new_name); + } + }`, + ), + ).toMatchSnapshot(); + }); }); - it("block form with implicit $value", () => { - expect( - test_parser.parseEval( - `class BookViewModel { + describe("block form", () => { + it("with implicit $value", () => { + expect( + test_parser.parseEval( + `class BookViewModel { public string $credits { set { $tmp = $this->credits + 1; @@ -76,8 +103,39 @@ describe("classpropertyhooks", () => { } } }`, - ), - ).toMatchSnapshot(); + ), + ).toMatchSnapshot(); + }); + + it("with explicit untyped $value", () => { + expect( + test_parser.parseEval( + `class BookViewModel { + public $credits { + set ($value) { + $tmp = $this->credits + 1; + $this->propertyName = $value + $tmp; + } + } + }`, + ), + ).toMatchSnapshot(); + }); + + it("with explicit typed $value", () => { + expect( + test_parser.parseEval( + `class BookViewModel { + public int $credits { + set (int $value) { + $tmp = $this->credits + 1; + $this->propertyName = $value + $tmp; + } + } + }`, + ), + ).toMatchSnapshot(); + }); }); }); }); From 61676380762f32801e8a73bbc4ad7a1b2cb2334a Mon Sep 17 00:00:00 2001 From: Thomas Genin Date: Sun, 1 Dec 2024 14:06:44 -0800 Subject: [PATCH 09/17] default value --- src/parser/class.js | 2 +- .../classpropertyhooks.test.js.snap | 73 +++++++++++++++++++ test/snapshot/classpropertyhooks.test.js | 12 +++ 3 files changed, 86 insertions(+), 1 deletion(-) diff --git a/src/parser/class.js b/src/parser/class.js index 672837a54..90bac40b2 100644 --- a/src/parser/class.js +++ b/src/parser/class.js @@ -212,7 +212,7 @@ module.exports = { } // Property is using a hook to define getter/setters - else if (this.token === "{") { + if (this.token === "{") { this.next(); property_hooks = this.read_property_hooks(); } else { diff --git a/test/snapshot/__snapshots__/classpropertyhooks.test.js.snap b/test/snapshot/__snapshots__/classpropertyhooks.test.js.snap index c20161e5f..589cbabcc 100644 --- a/test/snapshot/__snapshots__/classpropertyhooks.test.js.snap +++ b/test/snapshot/__snapshots__/classpropertyhooks.test.js.snap @@ -844,3 +844,76 @@ Program { "kind": "program", } `; + +exports[`classpropertyhooks support default value 1`] = ` +Program { + "children": [ + Class { + "attrGroups": [], + "body": [ + PropertyStatement { + "isStatic": false, + "kind": "propertystatement", + "properties": [ + Property { + "attrGroups": [], + "hooks": [ + PropertyHook { + "body": PropertyLookup { + "kind": "propertylookup", + "offset": Identifier { + "kind": "identifier", + "name": "foo", + }, + "what": Variable { + "curly": false, + "kind": "variable", + "name": "this", + }, + }, + "kind": "propertyhook", + "name": "get", + "parameter": null, + }, + ], + "kind": "property", + "name": Identifier { + "kind": "identifier", + "name": "foo", + }, + "nullable": false, + "readonly": false, + "type": TypeReference { + "kind": "typereference", + "name": "string", + "raw": "string", + }, + "value": String { + "isDoubleQuote": false, + "kind": "string", + "raw": "'default value'", + "unicode": false, + "value": "default value", + }, + }, + ], + "visibility": "public", + }, + ], + "extends": null, + "implements": null, + "isAbstract": false, + "isAnonymous": false, + "isFinal": false, + "isReadonly": false, + "kind": "class", + "name": Identifier { + "kind": "identifier", + "name": "Example", + }, + }, + ], + "errors": [], + "kind": "program", +} +`; diff --git a/test/snapshot/classpropertyhooks.test.js b/test/snapshot/classpropertyhooks.test.js index 8d85c4fb3..99116a6d0 100644 --- a/test/snapshot/classpropertyhooks.test.js +++ b/test/snapshot/classpropertyhooks.test.js @@ -138,4 +138,16 @@ describe("classpropertyhooks", () => { }); }); }); + + it("support default value", () => { + expect( + test_parser.parseEval( + `class Example { + public string $foo = 'default value' { + get => $this->foo ; + } +}`, + ), + ).toMatchSnapshot(); + }); }); From f264d74bfc3239b60714037812cb945fe29cce52 Mon Sep 17 00:00:00 2001 From: Thomas Genin Date: Sun, 1 Dec 2024 14:59:33 -0800 Subject: [PATCH 10/17] more tests --- src/parser/class.js | 72 ++- .../classpropertyhooks.test.js.snap | 551 ++++++++++++++++++ test/snapshot/classpropertyhooks.test.js | 37 ++ 3 files changed, 628 insertions(+), 32 deletions(-) diff --git a/src/parser/class.js b/src/parser/class.js index 90bac40b2..04b28a4ca 100644 --- a/src/parser/class.js +++ b/src/parser/class.js @@ -242,46 +242,22 @@ module.exports = { } return property_statement; }, + /** - * Reads a property hooks - * @returns {[null,null]} + * Reads property hooks + * + * @returns {PropertyHook[]} */ read_property_hooks: function () { if (this.version < 804) { this.raiseError("Parse Error: Typed Class Constants requires PHP 8.4+"); } - const hooks = this.read_list(function read_property_hook() { - const property_hooks = this.node("propertyhook"); - const method_name = this.text(); - if (method_name !== "get" && method_name !== "set") { - this.raiseError( - "Parse Error: Property hooks must be either 'get' or 'set'", - ); - } - this.next(); - - let parameter = null; - let body = null; - this.expect([this.tok.T_DOUBLE_ARROW, "{", "("]); - - if (this.token === "(") { - this.next(); - parameter = this.read_parameter(false); - this.expect(")"); - this.next(); - } - - if (this.token === this.tok.T_DOUBLE_ARROW) { - this.next(); - body = this.read_expr(); - this.next(); - } else if (this.token === "{") { - body = this.read_code_block(); - } + const hooks = []; - return property_hooks(method_name, parameter, body); - }, ","); + while (this.token !== "}") { + hooks.push(this.read_property_hook()); + } if (this.token === "}") { this.next(); @@ -290,6 +266,38 @@ module.exports = { return null; }, + read_property_hook: function () { + const property_hooks = this.node("propertyhook"); + const method_name = this.text(); + if (method_name !== "get" && method_name !== "set") { + this.raiseError( + "Parse Error: Property hooks must be either 'get' or 'set'", + ); + } + this.next(); + + let parameter = null; + let body = null; + this.expect([this.tok.T_DOUBLE_ARROW, "{", "("]); + + if (this.token === "(") { + this.next(); + parameter = this.read_parameter(false); + this.expect(")"); + this.next(); + } + + if (this.token === this.tok.T_DOUBLE_ARROW) { + this.next(); + body = this.read_expr(); + this.next(); + } else if (this.token === "{") { + body = this.read_code_block(); + } + + return property_hooks(method_name, parameter, body); + }, + /* * Reads constant list * ```ebnf diff --git a/test/snapshot/__snapshots__/classpropertyhooks.test.js.snap b/test/snapshot/__snapshots__/classpropertyhooks.test.js.snap index 589cbabcc..c83df41cf 100644 --- a/test/snapshot/__snapshots__/classpropertyhooks.test.js.snap +++ b/test/snapshot/__snapshots__/classpropertyhooks.test.js.snap @@ -917,3 +917,554 @@ Program { "kind": "program", } `; + +exports[`classpropertyhooks support setter + getter 1`] = ` +Program { + "children": [ + Class { + "attrGroups": [], + "body": [ + PropertyStatement { + "isStatic": false, + "kind": "propertystatement", + "properties": [ + Property { + "attrGroups": [], + "hooks": null, + "kind": "property", + "name": Identifier { + "kind": "identifier", + "name": "modified", + }, + "nullable": false, + "readonly": false, + "type": TypeReference { + "kind": "typereference", + "name": "bool", + "raw": "bool", + }, + "value": Boolean { + "kind": "boolean", + "raw": "false", + "value": false, + }, + }, + ], + "visibility": "private", + }, + PropertyStatement { + "isStatic": false, + "kind": "propertystatement", + "properties": [ + Property { + "attrGroups": [], + "hooks": [ + PropertyHook { + "body": Bin { + "kind": "bin", + "left": PropertyLookup { + "kind": "propertylookup", + "offset": Identifier { + "kind": "identifier", + "name": "foo", + }, + "what": Variable { + "curly": false, + "kind": "variable", + "name": "this", + }, + }, + "right": RetIf { + "falseExpr": String { + "isDoubleQuote": false, + "kind": "string", + "raw": "''", + "unicode": false, + "value": "", + }, + "kind": "retif", + "parenthesizedExpression": true, + "test": PropertyLookup { + "kind": "propertylookup", + "offset": Identifier { + "kind": "identifier", + "name": "modified", + }, + "what": Variable { + "curly": false, + "kind": "variable", + "name": "this", + }, + }, + "trueExpr": String { + "isDoubleQuote": false, + "kind": "string", + "raw": "' (modified)'", + "unicode": false, + "value": " (modified)", + }, + }, + "type": ".", + }, + "kind": "propertyhook", + "name": "get", + "parameter": null, + }, + PropertyHook { + "body": Block { + "children": [ + ExpressionStatement { + "expression": Assign { + "kind": "assign", + "left": PropertyLookup { + "kind": "propertylookup", + "offset": Identifier { + "kind": "identifier", + "name": "foo", + }, + "what": Variable { + "curly": false, + "kind": "variable", + "name": "this", + }, + }, + "operator": "=", + "right": Call { + "arguments": [ + Variable { + "curly": false, + "kind": "variable", + "name": "value", + }, + ], + "kind": "call", + "what": Name { + "kind": "name", + "name": "strtolower", + "resolution": "uqn", + }, + }, + }, + "kind": "expressionstatement", + }, + ExpressionStatement { + "expression": Assign { + "kind": "assign", + "left": PropertyLookup { + "kind": "propertylookup", + "offset": Identifier { + "kind": "identifier", + "name": "modified", + }, + "what": Variable { + "curly": false, + "kind": "variable", + "name": "this", + }, + }, + "operator": "=", + "right": Boolean { + "kind": "boolean", + "raw": "true", + "value": true, + }, + }, + "kind": "expressionstatement", + }, + ], + "kind": "block", + }, + "kind": "propertyhook", + "name": "set", + "parameter": Parameter { + "attrGroups": [], + "byref": false, + "flags": 0, + "kind": "parameter", + "name": Identifier { + "kind": "identifier", + "name": "value", + }, + "nullable": false, + "readonly": false, + "type": TypeReference { + "kind": "typereference", + "name": "string", + "raw": "string", + }, + "value": null, + "variadic": false, + }, + }, + ], + "kind": "property", + "name": Identifier { + "kind": "identifier", + "name": "foo", + }, + "nullable": false, + "readonly": false, + "type": TypeReference { + "kind": "typereference", + "name": "string", + "raw": "string", + }, + "value": String { + "isDoubleQuote": false, + "kind": "string", + "raw": "'default value'", + "unicode": false, + "value": "default value", + }, + }, + ], + "visibility": "public", + }, + ], + "extends": null, + "implements": null, + "isAbstract": false, + "isAnonymous": false, + "isFinal": false, + "isReadonly": false, + "kind": "class", + "name": Identifier { + "kind": "identifier", + "name": "Example", + }, + }, + ], + "errors": [], + "kind": "program", +} +`; + +exports[`classpropertyhooks support setter + getter 2`] = ` +Program { + "children": [ + Class { + "attrGroups": [], + "body": [ + PropertyStatement { + "isStatic": false, + "kind": "propertystatement", + "properties": [ + Property { + "attrGroups": [], + "hooks": null, + "kind": "property", + "name": Identifier { + "kind": "identifier", + "name": "modified", + }, + "nullable": false, + "readonly": false, + "type": TypeReference { + "kind": "typereference", + "name": "bool", + "raw": "bool", + }, + "value": Boolean { + "kind": "boolean", + "raw": "false", + "value": false, + }, + }, + ], + "visibility": "private", + }, + PropertyStatement { + "isStatic": false, + "kind": "propertystatement", + "properties": [ + Property { + "attrGroups": [], + "hooks": [ + PropertyHook { + "body": Bin { + "kind": "bin", + "left": PropertyLookup { + "kind": "propertylookup", + "offset": Identifier { + "kind": "identifier", + "name": "foo", + }, + "what": Variable { + "curly": false, + "kind": "variable", + "name": "this", + }, + }, + "right": RetIf { + "falseExpr": String { + "isDoubleQuote": false, + "kind": "string", + "raw": "''", + "unicode": false, + "value": "", + }, + "kind": "retif", + "parenthesizedExpression": true, + "test": PropertyLookup { + "kind": "propertylookup", + "offset": Identifier { + "kind": "identifier", + "name": "modified", + }, + "what": Variable { + "curly": false, + "kind": "variable", + "name": "this", + }, + }, + "trueExpr": String { + "isDoubleQuote": false, + "kind": "string", + "raw": "' (modified)'", + "unicode": false, + "value": " (modified)", + }, + }, + "type": ".", + }, + "kind": "propertyhook", + "name": "get", + "parameter": null, + }, + PropertyHook { + "body": Block { + "children": [ + ExpressionStatement { + "expression": Assign { + "kind": "assign", + "left": PropertyLookup { + "kind": "propertylookup", + "offset": Identifier { + "kind": "identifier", + "name": "foo", + }, + "what": Variable { + "curly": false, + "kind": "variable", + "name": "this", + }, + }, + "operator": "=", + "right": Call { + "arguments": [ + Variable { + "curly": false, + "kind": "variable", + "name": "value", + }, + ], + "kind": "call", + "what": Name { + "kind": "name", + "name": "strtolower", + "resolution": "uqn", + }, + }, + }, + "kind": "expressionstatement", + }, + ExpressionStatement { + "expression": Assign { + "kind": "assign", + "left": PropertyLookup { + "kind": "propertylookup", + "offset": Identifier { + "kind": "identifier", + "name": "modified", + }, + "what": Variable { + "curly": false, + "kind": "variable", + "name": "this", + }, + }, + "operator": "=", + "right": Boolean { + "kind": "boolean", + "raw": "true", + "value": true, + }, + }, + "kind": "expressionstatement", + }, + ], + "kind": "block", + }, + "kind": "propertyhook", + "name": "set", + "parameter": null, + }, + ], + "kind": "property", + "name": Identifier { + "kind": "identifier", + "name": "foo", + }, + "nullable": false, + "readonly": false, + "type": TypeReference { + "kind": "typereference", + "name": "string", + "raw": "string", + }, + "value": String { + "isDoubleQuote": false, + "kind": "string", + "raw": "'default value'", + "unicode": false, + "value": "default value", + }, + }, + ], + "visibility": "public", + }, + ], + "extends": null, + "implements": null, + "isAbstract": false, + "isAnonymous": false, + "isFinal": false, + "isReadonly": false, + "kind": "class", + "name": Identifier { + "kind": "identifier", + "name": "Example", + }, + }, + ], + "errors": [], + "kind": "program", +} +`; + +exports[`classpropertyhooks support setter + getter 3`] = ` +Program { + "children": [ + Class { + "attrGroups": [], + "body": [ + PropertyStatement { + "isStatic": false, + "kind": "propertystatement", + "properties": [ + Property { + "attrGroups": [], + "hooks": [ + PropertyHook { + "body": Bin { + "kind": "bin", + "left": PropertyLookup { + "kind": "propertylookup", + "offset": Identifier { + "kind": "identifier", + "name": "foo", + }, + "what": Variable { + "curly": false, + "kind": "variable", + "name": "this", + }, + }, + "right": RetIf { + "falseExpr": String { + "isDoubleQuote": false, + "kind": "string", + "raw": "''", + "unicode": false, + "value": "", + }, + "kind": "retif", + "parenthesizedExpression": true, + "test": PropertyLookup { + "kind": "propertylookup", + "offset": Identifier { + "kind": "identifier", + "name": "modified", + }, + "what": Variable { + "curly": false, + "kind": "variable", + "name": "this", + }, + }, + "trueExpr": String { + "isDoubleQuote": false, + "kind": "string", + "raw": "' (modified)'", + "unicode": false, + "value": " (modified)", + }, + }, + "type": ".", + }, + "kind": "propertyhook", + "name": "get", + "parameter": null, + }, + PropertyHook { + "body": Call { + "arguments": [ + Variable { + "curly": false, + "kind": "variable", + "name": "value", + }, + ], + "kind": "call", + "what": Name { + "kind": "name", + "name": "strtolower", + "resolution": "uqn", + }, + }, + "kind": "propertyhook", + "name": "set", + "parameter": null, + }, + ], + "kind": "property", + "name": Identifier { + "kind": "identifier", + "name": "foo", + }, + "nullable": false, + "readonly": false, + "type": TypeReference { + "kind": "typereference", + "name": "string", + "raw": "string", + }, + "value": String { + "isDoubleQuote": false, + "kind": "string", + "raw": "'default value'", + "unicode": false, + "value": "default value", + }, + }, + ], + "visibility": "public", + }, + ], + "extends": null, + "implements": null, + "isAbstract": false, + "isAnonymous": false, + "isFinal": false, + "isReadonly": false, + "kind": "class", + "name": Identifier { + "kind": "identifier", + "name": "Example", + }, + }, + ], + "errors": [], + "kind": "program", +} +`; diff --git a/test/snapshot/classpropertyhooks.test.js b/test/snapshot/classpropertyhooks.test.js index 99116a6d0..e90b54f5d 100644 --- a/test/snapshot/classpropertyhooks.test.js +++ b/test/snapshot/classpropertyhooks.test.js @@ -150,4 +150,41 @@ describe("classpropertyhooks", () => { ), ).toMatchSnapshot(); }); + + [ + `class Example { + private bool $modified = false; + public string $foo = 'default value' { + get => $this->foo . ($this->modified ? ' (modified)' : ''); + set(string $value) { + $this->foo = strtolower($value); + $this->modified = true; + } + } +}`, + `class Example +{ + private bool $modified = false; + + public string $foo = 'default value' { + get => $this->foo . ($this->modified ? ' (modified)' : ''); + + set { + $this->foo = strtolower($value); + $this->modified = true; + } + } +}`, + `class Example +{ + public string $foo = 'default value' { + get => $this->foo . ($this->modified ? ' (modified)' : ''); + set => strtolower($value); + } +}`, + ].forEach((code) => { + it("support setter + getter", () => { + expect(test_parser.parseEval(code)).toMatchSnapshot(); + }); + }); }); From 5822e0aba2a0fecb87e252d311b2d931554880a8 Mon Sep 17 00:00:00 2001 From: Thomas Genin Date: Sun, 1 Dec 2024 15:15:43 -0800 Subject: [PATCH 11/17] by ref --- src/ast/propertyhook.js | 4 +- src/parser/class.js | 10 +- .../classpropertyhooks.test.js.snap | 152 ++++++++++++++++++ test/snapshot/classpropertyhooks.test.js | 16 ++ 4 files changed, 179 insertions(+), 3 deletions(-) diff --git a/src/ast/propertyhook.js b/src/ast/propertyhook.js index e3a831492..59ae7ec65 100644 --- a/src/ast/propertyhook.js +++ b/src/ast/propertyhook.js @@ -15,14 +15,16 @@ const KIND = "propertyhook"; * @memberOf module:php-parser * @extends {Statement} * @property {string} name + * @property {Boolean} byref * @property {Parameter|null} parameter * @property {Block|Statement} body */ module.exports = Statement.extends( KIND, - function PropertyHook(name, parameter, body, docs, location) { + function PropertyHook(name, byref, parameter, body, docs, location) { Statement.apply(this, [KIND, docs, location]); this.name = name; + this.byref = byref; this.parameter = parameter; this.body = body; }, diff --git a/src/parser/class.js b/src/parser/class.js index 04b28a4ca..b3db88e84 100644 --- a/src/parser/class.js +++ b/src/parser/class.js @@ -268,10 +268,16 @@ module.exports = { read_property_hook: function () { const property_hooks = this.node("propertyhook"); + + const is_reference = this.token === "&"; + if (is_reference) this.next(); + const method_name = this.text(); + if (method_name !== "get" && method_name !== "set") { this.raiseError( - "Parse Error: Property hooks must be either 'get' or 'set'", + "Parse Error: Property hooks must be either 'get' or 'set'" + + this.token, ); } this.next(); @@ -295,7 +301,7 @@ module.exports = { body = this.read_code_block(); } - return property_hooks(method_name, parameter, body); + return property_hooks(method_name, is_reference, parameter, body); }, /* diff --git a/test/snapshot/__snapshots__/classpropertyhooks.test.js.snap b/test/snapshot/__snapshots__/classpropertyhooks.test.js.snap index c83df41cf..ae0845fee 100644 --- a/test/snapshot/__snapshots__/classpropertyhooks.test.js.snap +++ b/test/snapshot/__snapshots__/classpropertyhooks.test.js.snap @@ -1,5 +1,142 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`classpropertyhooks can be access by reference 1`] = ` +Program { + "children": [ + Class { + "attrGroups": [], + "body": [ + PropertyStatement { + "isStatic": false, + "kind": "propertystatement", + "properties": [ + Property { + "attrGroups": [], + "hooks": null, + "kind": "property", + "name": Identifier { + "kind": "identifier", + "name": "_baz", + }, + "nullable": false, + "readonly": false, + "type": TypeReference { + "kind": "typereference", + "name": "string", + "raw": "string", + }, + "value": null, + }, + ], + "visibility": "private", + }, + PropertyStatement { + "isStatic": false, + "kind": "propertystatement", + "properties": [ + Property { + "attrGroups": [], + "hooks": [ + PropertyHook { + "body": PropertyLookup { + "kind": "propertylookup", + "offset": Identifier { + "kind": "identifier", + "name": "_baz", + }, + "what": Variable { + "curly": false, + "kind": "variable", + "name": "this", + }, + }, + "byref": true, + "kind": "propertyhook", + "name": "get", + "parameter": null, + }, + PropertyHook { + "body": Block { + "children": [ + ExpressionStatement { + "expression": Assign { + "kind": "assign", + "left": PropertyLookup { + "kind": "propertylookup", + "offset": Identifier { + "kind": "identifier", + "name": "_baz", + }, + "what": Variable { + "curly": false, + "kind": "variable", + "name": "this", + }, + }, + "operator": "=", + "right": Call { + "arguments": [ + Variable { + "curly": false, + "kind": "variable", + "name": "value", + }, + ], + "kind": "call", + "what": Name { + "kind": "name", + "name": "strtoupper", + "resolution": "uqn", + }, + }, + }, + "kind": "expressionstatement", + }, + ], + "kind": "block", + }, + "byref": false, + "kind": "propertyhook", + "name": "set", + "parameter": null, + }, + ], + "kind": "property", + "name": Identifier { + "kind": "identifier", + "name": "baz", + }, + "nullable": false, + "readonly": false, + "type": TypeReference { + "kind": "typereference", + "name": "string", + "raw": "string", + }, + "value": null, + }, + ], + "visibility": "public", + }, + ], + "extends": null, + "implements": null, + "isAbstract": false, + "isAnonymous": false, + "isFinal": false, + "isReadonly": false, + "kind": "class", + "name": Identifier { + "kind": "identifier", + "name": "Foo", + }, + }, + ], + "errors": [], + "kind": "program", +} +`; + exports[`classpropertyhooks getter arrow function 1`] = ` Program { "children": [ @@ -37,6 +174,7 @@ Program { }, "type": ".", }, + "byref": false, "kind": "propertyhook", "name": "get", "parameter": null, @@ -123,6 +261,7 @@ Program { ], "kind": "block", }, + "byref": false, "kind": "propertyhook", "name": "get", "parameter": null, @@ -250,6 +389,7 @@ Program { ], "kind": "block", }, + "byref": false, "kind": "propertyhook", "name": "set", "parameter": Parameter { @@ -393,6 +533,7 @@ Program { ], "kind": "block", }, + "byref": false, "kind": "propertyhook", "name": "set", "parameter": Parameter { @@ -528,6 +669,7 @@ Program { ], "kind": "block", }, + "byref": false, "kind": "propertyhook", "name": "set", "parameter": null, @@ -614,6 +756,7 @@ Program { }, }, }, + "byref": false, "kind": "propertyhook", "name": "set", "parameter": Parameter { @@ -718,6 +861,7 @@ Program { }, }, }, + "byref": false, "kind": "propertyhook", "name": "set", "parameter": Parameter { @@ -804,6 +948,7 @@ Program { "name": "value", }, }, + "byref": false, "kind": "propertyhook", "name": "set", "parameter": null, @@ -871,6 +1016,7 @@ Program { "name": "this", }, }, + "byref": false, "kind": "propertyhook", "name": "get", "parameter": null, @@ -1006,6 +1152,7 @@ Program { }, "type": ".", }, + "byref": false, "kind": "propertyhook", "name": "get", "parameter": null, @@ -1074,6 +1221,7 @@ Program { ], "kind": "block", }, + "byref": false, "kind": "propertyhook", "name": "set", "parameter": Parameter { @@ -1227,6 +1375,7 @@ Program { }, "type": ".", }, + "byref": false, "kind": "propertyhook", "name": "get", "parameter": null, @@ -1295,6 +1444,7 @@ Program { ], "kind": "block", }, + "byref": false, "kind": "propertyhook", "name": "set", "parameter": null, @@ -1402,6 +1552,7 @@ Program { }, "type": ".", }, + "byref": false, "kind": "propertyhook", "name": "get", "parameter": null, @@ -1422,6 +1573,7 @@ Program { "resolution": "uqn", }, }, + "byref": false, "kind": "propertyhook", "name": "set", "parameter": null, diff --git a/test/snapshot/classpropertyhooks.test.js b/test/snapshot/classpropertyhooks.test.js index e90b54f5d..9a997e6bf 100644 --- a/test/snapshot/classpropertyhooks.test.js +++ b/test/snapshot/classpropertyhooks.test.js @@ -187,4 +187,20 @@ describe("classpropertyhooks", () => { expect(test_parser.parseEval(code)).toMatchSnapshot(); }); }); + + it("can be access by reference", () => { + expect( + test_parser.parseEval(`class Foo +{ + private string $_baz; + + public string $baz { + &get => $this->_baz; + set { + $this->_baz = strtoupper($value); + } + } +}`), + ).toMatchSnapshot(); + }); }); From 17f7d7d6e1d2f2fb4a7b6ff65211bc7374a2449b Mon Sep 17 00:00:00 2001 From: Thomas Genin Date: Sun, 1 Dec 2024 15:24:08 -0800 Subject: [PATCH 12/17] support final on hook --- src/ast/propertyhook.js | 3 +- src/parser/class.js | 8 +- .../classpropertyhooks.test.js.snap | 162 ++++++++++++++++++ test/snapshot/classpropertyhooks.test.js | 16 ++ 4 files changed, 185 insertions(+), 4 deletions(-) diff --git a/src/ast/propertyhook.js b/src/ast/propertyhook.js index 59ae7ec65..760508003 100644 --- a/src/ast/propertyhook.js +++ b/src/ast/propertyhook.js @@ -21,11 +21,12 @@ const KIND = "propertyhook"; */ module.exports = Statement.extends( KIND, - function PropertyHook(name, byref, parameter, body, docs, location) { + function PropertyHook(name, isFinal, byref, parameter, body, docs, location) { Statement.apply(this, [KIND, docs, location]); this.name = name; this.byref = byref; this.parameter = parameter; this.body = body; + this.isFinal = isFinal; }, ); diff --git a/src/parser/class.js b/src/parser/class.js index b3db88e84..275f82c51 100644 --- a/src/parser/class.js +++ b/src/parser/class.js @@ -269,6 +269,9 @@ module.exports = { read_property_hook: function () { const property_hooks = this.node("propertyhook"); + const is_final = this.token === this.tok.T_FINAL; + if (is_final) this.next(); + const is_reference = this.token === "&"; if (is_reference) this.next(); @@ -276,8 +279,7 @@ module.exports = { if (method_name !== "get" && method_name !== "set") { this.raiseError( - "Parse Error: Property hooks must be either 'get' or 'set'" + - this.token, + "Parse Error: Property hooks must be either 'get' or 'set'", ); } this.next(); @@ -301,7 +303,7 @@ module.exports = { body = this.read_code_block(); } - return property_hooks(method_name, is_reference, parameter, body); + return property_hooks(method_name, is_final, is_reference, parameter, body); }, /* diff --git a/test/snapshot/__snapshots__/classpropertyhooks.test.js.snap b/test/snapshot/__snapshots__/classpropertyhooks.test.js.snap index ae0845fee..664295d9b 100644 --- a/test/snapshot/__snapshots__/classpropertyhooks.test.js.snap +++ b/test/snapshot/__snapshots__/classpropertyhooks.test.js.snap @@ -51,6 +51,7 @@ Program { }, }, "byref": true, + "isFinal": false, "kind": "propertyhook", "name": "get", "parameter": null, @@ -96,6 +97,7 @@ Program { "kind": "block", }, "byref": false, + "isFinal": false, "kind": "propertyhook", "name": "set", "parameter": null, @@ -175,6 +177,7 @@ Program { "type": ".", }, "byref": false, + "isFinal": false, "kind": "propertyhook", "name": "get", "parameter": null, @@ -262,6 +265,7 @@ Program { "kind": "block", }, "byref": false, + "isFinal": false, "kind": "propertyhook", "name": "get", "parameter": null, @@ -390,6 +394,7 @@ Program { "kind": "block", }, "byref": false, + "isFinal": false, "kind": "propertyhook", "name": "set", "parameter": Parameter { @@ -534,6 +539,7 @@ Program { "kind": "block", }, "byref": false, + "isFinal": false, "kind": "propertyhook", "name": "set", "parameter": Parameter { @@ -670,6 +676,7 @@ Program { "kind": "block", }, "byref": false, + "isFinal": false, "kind": "propertyhook", "name": "set", "parameter": null, @@ -757,6 +764,7 @@ Program { }, }, "byref": false, + "isFinal": false, "kind": "propertyhook", "name": "set", "parameter": Parameter { @@ -862,6 +870,7 @@ Program { }, }, "byref": false, + "isFinal": false, "kind": "propertyhook", "name": "set", "parameter": Parameter { @@ -949,6 +958,7 @@ Program { }, }, "byref": false, + "isFinal": false, "kind": "propertyhook", "name": "set", "parameter": null, @@ -1017,6 +1027,7 @@ Program { }, }, "byref": false, + "isFinal": false, "kind": "propertyhook", "name": "get", "parameter": null, @@ -1064,6 +1075,151 @@ Program { } `; +exports[`classpropertyhooks support final on the hook itself 1`] = ` +Program { + "children": [ + Class { + "attrGroups": [], + "body": [ + PropertyStatement { + "isStatic": false, + "kind": "propertystatement", + "properties": [ + Property { + "attrGroups": [], + "hooks": [ + PropertyHook { + "body": Block { + "children": [ + If { + "alternate": null, + "body": Block { + "children": [ + Throw { + "kind": "throw", + "what": New { + "arguments": [ + String { + "isDoubleQuote": false, + "kind": "string", + "raw": "'Invalid email'", + "unicode": false, + "value": "Invalid email", + }, + ], + "kind": "new", + "what": Name { + "kind": "name", + "name": "InvalidArgumentException", + "resolution": "uqn", + }, + }, + }, + ], + "kind": "block", + }, + "kind": "if", + "shortForm": false, + "test": Unary { + "kind": "unary", + "type": "!", + "what": Call { + "arguments": [ + Variable { + "curly": false, + "kind": "variable", + "name": "value", + }, + Name { + "kind": "name", + "name": "FILTER_VALIDATE_EMAIL", + "resolution": "uqn", + }, + Name { + "kind": "name", + "name": "FILTER_FLAG_EMAIL_UNICODE", + "resolution": "uqn", + }, + ], + "kind": "call", + "what": Name { + "kind": "name", + "name": "filter_var", + "resolution": "uqn", + }, + }, + }, + }, + ExpressionStatement { + "expression": Assign { + "kind": "assign", + "left": PropertyLookup { + "kind": "propertylookup", + "offset": Identifier { + "kind": "identifier", + "name": "email", + }, + "what": Variable { + "curly": false, + "kind": "variable", + "name": "this", + }, + }, + "operator": "=", + "right": Variable { + "curly": false, + "kind": "variable", + "name": "value", + }, + }, + "kind": "expressionstatement", + }, + ], + "kind": "block", + }, + "byref": false, + "isFinal": true, + "kind": "propertyhook", + "name": "set", + "parameter": null, + }, + ], + "kind": "property", + "name": Identifier { + "kind": "identifier", + "name": "email", + }, + "nullable": false, + "readonly": false, + "type": TypeReference { + "kind": "typereference", + "name": "string", + "raw": "string", + }, + "value": null, + }, + ], + "visibility": "public", + }, + ], + "extends": null, + "implements": null, + "isAbstract": false, + "isAnonymous": false, + "isFinal": false, + "isReadonly": false, + "kind": "class", + "name": Identifier { + "kind": "identifier", + "name": "StandardUser", + }, + }, + ], + "errors": [], + "kind": "program", +} +`; + exports[`classpropertyhooks support setter + getter 1`] = ` Program { "children": [ @@ -1153,6 +1309,7 @@ Program { "type": ".", }, "byref": false, + "isFinal": false, "kind": "propertyhook", "name": "get", "parameter": null, @@ -1222,6 +1379,7 @@ Program { "kind": "block", }, "byref": false, + "isFinal": false, "kind": "propertyhook", "name": "set", "parameter": Parameter { @@ -1376,6 +1534,7 @@ Program { "type": ".", }, "byref": false, + "isFinal": false, "kind": "propertyhook", "name": "get", "parameter": null, @@ -1445,6 +1604,7 @@ Program { "kind": "block", }, "byref": false, + "isFinal": false, "kind": "propertyhook", "name": "set", "parameter": null, @@ -1553,6 +1713,7 @@ Program { "type": ".", }, "byref": false, + "isFinal": false, "kind": "propertyhook", "name": "get", "parameter": null, @@ -1574,6 +1735,7 @@ Program { }, }, "byref": false, + "isFinal": false, "kind": "propertyhook", "name": "set", "parameter": null, diff --git a/test/snapshot/classpropertyhooks.test.js b/test/snapshot/classpropertyhooks.test.js index 9a997e6bf..2f3a3d387 100644 --- a/test/snapshot/classpropertyhooks.test.js +++ b/test/snapshot/classpropertyhooks.test.js @@ -200,6 +200,22 @@ describe("classpropertyhooks", () => { $this->_baz = strtoupper($value); } } +}`), + ).toMatchSnapshot(); + }); + + it("support final on the hook itself", () => { + expect( + test_parser.parseEval(`class StandardUser +{ + public string $email { + final set { + if (! filter_var($value, FILTER_VALIDATE_EMAIL, FILTER_FLAG_EMAIL_UNICODE)) { + throw new InvalidArgumentException('Invalid email'); + } + $this->email = $value; + } + } }`), ).toMatchSnapshot(); }); From 4ad938586a96df240a576b3cb81cfd1ae5d15b57 Mon Sep 17 00:00:00 2001 From: Thomas Genin Date: Sun, 1 Dec 2024 15:57:12 -0800 Subject: [PATCH 13/17] final + abstract --- src/ast/propertyhook.js | 1 + src/ast/propertystatement.js | 2 + src/parser/class.js | 7 +- .../classpropertyhooks.test.js.snap | 730 +++++++++++++----- test/snapshot/classpropertyhooks.test.js | 40 +- 5 files changed, 571 insertions(+), 209 deletions(-) diff --git a/src/ast/propertyhook.js b/src/ast/propertyhook.js index 760508003..a96282529 100644 --- a/src/ast/propertyhook.js +++ b/src/ast/propertyhook.js @@ -15,6 +15,7 @@ const KIND = "propertyhook"; * @memberOf module:php-parser * @extends {Statement} * @property {string} name + * @property {Boolean} isFinal * @property {Boolean} byref * @property {Parameter|null} parameter * @property {Block|Statement} body diff --git a/src/ast/propertystatement.js b/src/ast/propertystatement.js index 271c8e6a8..6cc46926d 100644 --- a/src/ast/propertystatement.js +++ b/src/ast/propertystatement.js @@ -52,6 +52,8 @@ PropertyStatement.prototype.parseFlags = function (flags) { } this.isStatic = flags[1] === 1; + this.isAbstract = flags[2] === 1; + this.isFinal = flags[2] === 2; }; module.exports = PropertyStatement; diff --git a/src/parser/class.js b/src/parser/class.js index 275f82c51..042ef363f 100644 --- a/src/parser/class.js +++ b/src/parser/class.js @@ -286,7 +286,12 @@ module.exports = { let parameter = null; let body = null; - this.expect([this.tok.T_DOUBLE_ARROW, "{", "("]); + this.expect([this.tok.T_DOUBLE_ARROW, "{", "(", ";"]); + + // interface or abstract definition + if (this.token === ";") { + this.next(); + } if (this.token === "(") { this.next(); diff --git a/test/snapshot/__snapshots__/classpropertyhooks.test.js.snap b/test/snapshot/__snapshots__/classpropertyhooks.test.js.snap index 664295d9b..4f3e43c43 100644 --- a/test/snapshot/__snapshots__/classpropertyhooks.test.js.snap +++ b/test/snapshot/__snapshots__/classpropertyhooks.test.js.snap @@ -1,5 +1,193 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`classpropertyhooks abstract get + set 1`] = ` +Program { + "children": [ + Class { + "attrGroups": [], + "body": [ + PropertyStatement { + "isAbstract": true, + "isFinal": false, + "isStatic": false, + "kind": "propertystatement", + "properties": [ + Property { + "attrGroups": [], + "hooks": [ + PropertyHook { + "body": null, + "byref": false, + "isFinal": false, + "kind": "propertyhook", + "name": "get", + "parameter": null, + }, + PropertyHook { + "body": null, + "byref": false, + "isFinal": false, + "kind": "propertyhook", + "name": "set", + "parameter": null, + }, + ], + "kind": "property", + "name": Identifier { + "kind": "identifier", + "name": "email", + }, + "nullable": false, + "readonly": false, + "type": TypeReference { + "kind": "typereference", + "name": "string", + "raw": "string", + }, + "value": null, + }, + ], + "visibility": "public", + }, + ], + "extends": null, + "implements": null, + "isAbstract": true, + "isAnonymous": false, + "isFinal": false, + "isReadonly": false, + "kind": "class", + "name": Identifier { + "kind": "identifier", + "name": "User", + }, + }, + ], + "errors": [], + "kind": "program", +} +`; + +exports[`classpropertyhooks abstract get 1`] = ` +Program { + "children": [ + Class { + "attrGroups": [], + "body": [ + PropertyStatement { + "isAbstract": true, + "isFinal": false, + "isStatic": false, + "kind": "propertystatement", + "properties": [ + Property { + "attrGroups": [], + "hooks": [ + PropertyHook { + "body": null, + "byref": false, + "isFinal": false, + "kind": "propertyhook", + "name": "get", + "parameter": null, + }, + ], + "kind": "property", + "name": Identifier { + "kind": "identifier", + "name": "email", + }, + "nullable": false, + "readonly": false, + "type": TypeReference { + "kind": "typereference", + "name": "string", + "raw": "string", + }, + "value": null, + }, + ], + "visibility": "public", + }, + ], + "extends": null, + "implements": null, + "isAbstract": true, + "isAnonymous": false, + "isFinal": false, + "isReadonly": false, + "kind": "class", + "name": Identifier { + "kind": "identifier", + "name": "User", + }, + }, + ], + "errors": [], + "kind": "program", +} +`; + +exports[`classpropertyhooks abstract set 1`] = ` +Program { + "children": [ + Class { + "attrGroups": [], + "body": [ + PropertyStatement { + "isAbstract": true, + "isFinal": false, + "isStatic": false, + "kind": "propertystatement", + "properties": [ + Property { + "attrGroups": [], + "hooks": [ + PropertyHook { + "body": null, + "byref": false, + "isFinal": false, + "kind": "propertyhook", + "name": "set", + "parameter": null, + }, + ], + "kind": "property", + "name": Identifier { + "kind": "identifier", + "name": "email", + }, + "nullable": false, + "readonly": false, + "type": TypeReference { + "kind": "typereference", + "name": "string", + "raw": "string", + }, + "value": null, + }, + ], + "visibility": "public", + }, + ], + "extends": null, + "implements": null, + "isAbstract": true, + "isAnonymous": false, + "isFinal": false, + "isReadonly": false, + "kind": "class", + "name": Identifier { + "kind": "identifier", + "name": "User", + }, + }, + ], + "errors": [], + "kind": "program", +} +`; + exports[`classpropertyhooks can be access by reference 1`] = ` Program { "children": [ @@ -7,6 +195,298 @@ Program { "attrGroups": [], "body": [ PropertyStatement { + "isAbstract": false, + "isFinal": false, + "isStatic": false, + "kind": "propertystatement", + "properties": [ + Property { + "attrGroups": [], + "hooks": null, + "kind": "property", + "name": Identifier { + "kind": "identifier", + "name": "_baz", + }, + "nullable": false, + "readonly": false, + "type": TypeReference { + "kind": "typereference", + "name": "string", + "raw": "string", + }, + "value": null, + }, + ], + "visibility": "private", + }, + PropertyStatement { + "isAbstract": false, + "isFinal": false, + "isStatic": false, + "kind": "propertystatement", + "properties": [ + Property { + "attrGroups": [], + "hooks": [ + PropertyHook { + "body": PropertyLookup { + "kind": "propertylookup", + "offset": Identifier { + "kind": "identifier", + "name": "_baz", + }, + "what": Variable { + "curly": false, + "kind": "variable", + "name": "this", + }, + }, + "byref": true, + "isFinal": false, + "kind": "propertyhook", + "name": "get", + "parameter": null, + }, + PropertyHook { + "body": Block { + "children": [ + ExpressionStatement { + "expression": Assign { + "kind": "assign", + "left": PropertyLookup { + "kind": "propertylookup", + "offset": Identifier { + "kind": "identifier", + "name": "_baz", + }, + "what": Variable { + "curly": false, + "kind": "variable", + "name": "this", + }, + }, + "operator": "=", + "right": Call { + "arguments": [ + Variable { + "curly": false, + "kind": "variable", + "name": "value", + }, + ], + "kind": "call", + "what": Name { + "kind": "name", + "name": "strtoupper", + "resolution": "uqn", + }, + }, + }, + "kind": "expressionstatement", + }, + ], + "kind": "block", + }, + "byref": false, + "isFinal": false, + "kind": "propertyhook", + "name": "set", + "parameter": null, + }, + ], + "kind": "property", + "name": Identifier { + "kind": "identifier", + "name": "baz", + }, + "nullable": false, + "readonly": false, + "type": TypeReference { + "kind": "typereference", + "name": "string", + "raw": "string", + }, + "value": null, + }, + ], + "visibility": "public", + }, + ], + "extends": null, + "implements": null, + "isAbstract": false, + "isAnonymous": false, + "isFinal": false, + "isReadonly": false, + "kind": "class", + "name": Identifier { + "kind": "identifier", + "name": "Foo", + }, + }, + ], + "errors": [], + "kind": "program", +} +`; + +exports[`classpropertyhooks final on the hook itself 1`] = ` +Program { + "children": [ + Class { + "attrGroups": [], + "body": [ + PropertyStatement { + "isAbstract": false, + "isFinal": false, + "isStatic": false, + "kind": "propertystatement", + "properties": [ + Property { + "attrGroups": [], + "hooks": [ + PropertyHook { + "body": Block { + "children": [ + If { + "alternate": null, + "body": Block { + "children": [ + Throw { + "kind": "throw", + "what": New { + "arguments": [ + String { + "isDoubleQuote": false, + "kind": "string", + "raw": "'Invalid email'", + "unicode": false, + "value": "Invalid email", + }, + ], + "kind": "new", + "what": Name { + "kind": "name", + "name": "InvalidArgumentException", + "resolution": "uqn", + }, + }, + }, + ], + "kind": "block", + }, + "kind": "if", + "shortForm": false, + "test": Unary { + "kind": "unary", + "type": "!", + "what": Call { + "arguments": [ + Variable { + "curly": false, + "kind": "variable", + "name": "value", + }, + Name { + "kind": "name", + "name": "FILTER_VALIDATE_EMAIL", + "resolution": "uqn", + }, + Name { + "kind": "name", + "name": "FILTER_FLAG_EMAIL_UNICODE", + "resolution": "uqn", + }, + ], + "kind": "call", + "what": Name { + "kind": "name", + "name": "filter_var", + "resolution": "uqn", + }, + }, + }, + }, + ExpressionStatement { + "expression": Assign { + "kind": "assign", + "left": PropertyLookup { + "kind": "propertylookup", + "offset": Identifier { + "kind": "identifier", + "name": "email", + }, + "what": Variable { + "curly": false, + "kind": "variable", + "name": "this", + }, + }, + "operator": "=", + "right": Variable { + "curly": false, + "kind": "variable", + "name": "value", + }, + }, + "kind": "expressionstatement", + }, + ], + "kind": "block", + }, + "byref": false, + "isFinal": true, + "kind": "propertyhook", + "name": "set", + "parameter": null, + }, + ], + "kind": "property", + "name": Identifier { + "kind": "identifier", + "name": "email", + }, + "nullable": false, + "readonly": false, + "type": TypeReference { + "kind": "typereference", + "name": "string", + "raw": "string", + }, + "value": null, + }, + ], + "visibility": "public", + }, + ], + "extends": null, + "implements": null, + "isAbstract": false, + "isAnonymous": false, + "isFinal": false, + "isReadonly": false, + "kind": "class", + "name": Identifier { + "kind": "identifier", + "name": "StandardUser", + }, + }, + ], + "errors": [], + "kind": "program", +} +`; + +exports[`classpropertyhooks final on the property 1`] = ` +Program { + "children": [ + Class { + "attrGroups": [], + "body": [ + PropertyStatement { + "isAbstract": false, + "isFinal": true, "isStatic": false, "kind": "propertystatement", "properties": [ @@ -16,7 +496,7 @@ Program { "kind": "property", "name": Identifier { "kind": "identifier", - "name": "_baz", + "name": "name", }, "nullable": false, "readonly": false, @@ -28,9 +508,11 @@ Program { "value": null, }, ], - "visibility": "private", + "visibility": "public", }, PropertyStatement { + "isAbstract": false, + "isFinal": true, "isStatic": false, "kind": "propertystatement", "properties": [ @@ -38,63 +520,20 @@ Program { "attrGroups": [], "hooks": [ PropertyHook { - "body": PropertyLookup { - "kind": "propertylookup", - "offset": Identifier { - "kind": "identifier", - "name": "_baz", - }, - "what": Variable { - "curly": false, - "kind": "variable", - "name": "this", - }, - }, - "byref": true, - "isFinal": false, - "kind": "propertyhook", - "name": "get", - "parameter": null, - }, - PropertyHook { - "body": Block { - "children": [ - ExpressionStatement { - "expression": Assign { - "kind": "assign", - "left": PropertyLookup { - "kind": "propertylookup", - "offset": Identifier { - "kind": "identifier", - "name": "_baz", - }, - "what": Variable { - "curly": false, - "kind": "variable", - "name": "this", - }, - }, - "operator": "=", - "right": Call { - "arguments": [ - Variable { - "curly": false, - "kind": "variable", - "name": "value", - }, - ], - "kind": "call", - "what": Name { - "kind": "name", - "name": "strtoupper", - "resolution": "uqn", - }, - }, - }, - "kind": "expressionstatement", + "body": Call { + "arguments": [ + Variable { + "curly": false, + "kind": "variable", + "name": "value", }, ], - "kind": "block", + "kind": "call", + "what": Name { + "kind": "name", + "name": "strtolower", + "resolution": "uqn", + }, }, "byref": false, "isFinal": false, @@ -106,7 +545,7 @@ Program { "kind": "property", "name": Identifier { "kind": "identifier", - "name": "baz", + "name": "username", }, "nullable": false, "readonly": false, @@ -130,7 +569,7 @@ Program { "kind": "class", "name": Identifier { "kind": "identifier", - "name": "Foo", + "name": "User", }, }, ], @@ -146,6 +585,8 @@ Program { "attrGroups": [], "body": [ PropertyStatement { + "isAbstract": false, + "isFinal": false, "isStatic": false, "kind": "propertystatement", "properties": [ @@ -226,6 +667,8 @@ Program { "attrGroups": [], "body": [ PropertyStatement { + "isAbstract": false, + "isFinal": false, "isStatic": false, "kind": "propertystatement", "properties": [ @@ -316,6 +759,8 @@ Program { "attrGroups": [], "body": [ PropertyStatement { + "isAbstract": false, + "isFinal": false, "isStatic": false, "kind": "propertystatement", "properties": [ @@ -461,6 +906,8 @@ Program { "attrGroups": [], "body": [ PropertyStatement { + "isAbstract": false, + "isFinal": false, "isStatic": false, "kind": "propertystatement", "properties": [ @@ -598,6 +1045,8 @@ Program { "attrGroups": [], "body": [ PropertyStatement { + "isAbstract": false, + "isFinal": false, "isStatic": false, "kind": "propertystatement", "properties": [ @@ -725,6 +1174,8 @@ Program { "attrGroups": [], "body": [ PropertyStatement { + "isAbstract": false, + "isFinal": false, "isStatic": false, "kind": "propertystatement", "properties": [ @@ -831,6 +1282,8 @@ Program { "attrGroups": [], "body": [ PropertyStatement { + "isAbstract": false, + "isFinal": false, "isStatic": false, "kind": "propertystatement", "properties": [ @@ -929,6 +1382,8 @@ Program { "attrGroups": [], "body": [ PropertyStatement { + "isAbstract": false, + "isFinal": false, "isStatic": false, "kind": "propertystatement", "properties": [ @@ -1007,6 +1462,8 @@ Program { "attrGroups": [], "body": [ PropertyStatement { + "isAbstract": false, + "isFinal": false, "isStatic": false, "kind": "propertystatement", "properties": [ @@ -1075,151 +1532,6 @@ Program { } `; -exports[`classpropertyhooks support final on the hook itself 1`] = ` -Program { - "children": [ - Class { - "attrGroups": [], - "body": [ - PropertyStatement { - "isStatic": false, - "kind": "propertystatement", - "properties": [ - Property { - "attrGroups": [], - "hooks": [ - PropertyHook { - "body": Block { - "children": [ - If { - "alternate": null, - "body": Block { - "children": [ - Throw { - "kind": "throw", - "what": New { - "arguments": [ - String { - "isDoubleQuote": false, - "kind": "string", - "raw": "'Invalid email'", - "unicode": false, - "value": "Invalid email", - }, - ], - "kind": "new", - "what": Name { - "kind": "name", - "name": "InvalidArgumentException", - "resolution": "uqn", - }, - }, - }, - ], - "kind": "block", - }, - "kind": "if", - "shortForm": false, - "test": Unary { - "kind": "unary", - "type": "!", - "what": Call { - "arguments": [ - Variable { - "curly": false, - "kind": "variable", - "name": "value", - }, - Name { - "kind": "name", - "name": "FILTER_VALIDATE_EMAIL", - "resolution": "uqn", - }, - Name { - "kind": "name", - "name": "FILTER_FLAG_EMAIL_UNICODE", - "resolution": "uqn", - }, - ], - "kind": "call", - "what": Name { - "kind": "name", - "name": "filter_var", - "resolution": "uqn", - }, - }, - }, - }, - ExpressionStatement { - "expression": Assign { - "kind": "assign", - "left": PropertyLookup { - "kind": "propertylookup", - "offset": Identifier { - "kind": "identifier", - "name": "email", - }, - "what": Variable { - "curly": false, - "kind": "variable", - "name": "this", - }, - }, - "operator": "=", - "right": Variable { - "curly": false, - "kind": "variable", - "name": "value", - }, - }, - "kind": "expressionstatement", - }, - ], - "kind": "block", - }, - "byref": false, - "isFinal": true, - "kind": "propertyhook", - "name": "set", - "parameter": null, - }, - ], - "kind": "property", - "name": Identifier { - "kind": "identifier", - "name": "email", - }, - "nullable": false, - "readonly": false, - "type": TypeReference { - "kind": "typereference", - "name": "string", - "raw": "string", - }, - "value": null, - }, - ], - "visibility": "public", - }, - ], - "extends": null, - "implements": null, - "isAbstract": false, - "isAnonymous": false, - "isFinal": false, - "isReadonly": false, - "kind": "class", - "name": Identifier { - "kind": "identifier", - "name": "StandardUser", - }, - }, - ], - "errors": [], - "kind": "program", -} -`; - exports[`classpropertyhooks support setter + getter 1`] = ` Program { "children": [ @@ -1227,6 +1539,8 @@ Program { "attrGroups": [], "body": [ PropertyStatement { + "isAbstract": false, + "isFinal": false, "isStatic": false, "kind": "propertystatement", "properties": [ @@ -1255,6 +1569,8 @@ Program { "visibility": "private", }, PropertyStatement { + "isAbstract": false, + "isFinal": false, "isStatic": false, "kind": "propertystatement", "properties": [ @@ -1452,6 +1768,8 @@ Program { "attrGroups": [], "body": [ PropertyStatement { + "isAbstract": false, + "isFinal": false, "isStatic": false, "kind": "propertystatement", "properties": [ @@ -1480,6 +1798,8 @@ Program { "visibility": "private", }, PropertyStatement { + "isAbstract": false, + "isFinal": false, "isStatic": false, "kind": "propertystatement", "properties": [ @@ -1659,6 +1979,8 @@ Program { "attrGroups": [], "body": [ PropertyStatement { + "isAbstract": false, + "isFinal": false, "isStatic": false, "kind": "propertystatement", "properties": [ diff --git a/test/snapshot/classpropertyhooks.test.js b/test/snapshot/classpropertyhooks.test.js index 2f3a3d387..36384874c 100644 --- a/test/snapshot/classpropertyhooks.test.js +++ b/test/snapshot/classpropertyhooks.test.js @@ -204,9 +204,10 @@ describe("classpropertyhooks", () => { ).toMatchSnapshot(); }); - it("support final on the hook itself", () => { - expect( - test_parser.parseEval(`class StandardUser + describe("final", () => { + it("on the hook itself", () => { + expect( + test_parser.parseEval(`class StandardUser { public string $email { final set { @@ -217,6 +218,37 @@ describe("classpropertyhooks", () => { } } }`), - ).toMatchSnapshot(); + ).toMatchSnapshot(); + }); + + it("on the property", () => { + const code = `class User { + // Child classes may not add hooks of any kind to this property. + public final string $name; + + // Child classes may not add any hooks or override set, + // but this set will still apply. + public final string $username { + set => strtolower($value); + } +}`; + expect(test_parser.parseEval(code)).toMatchSnapshot(); + }); + }); + + describe("abstract", () => { + [ + ["get", `abstract class User { abstract public string $email { get; } }`], + ["set", `abstract class User { abstract public string $email { set; } }`], + [ + "get + set", + `abstract class User { abstract public string $email { get; set; } }`, + ], + ].forEach(([name, code]) => { + // eslint-disable-next-line jest/valid-title + it(name, () => { + expect(test_parser.parseEval(code)).toMatchSnapshot(); + }); + }); }); }); From 3f2cfc37e256a2d6a113d78e5460463473eb0426 Mon Sep 17 00:00:00 2001 From: Thomas Genin Date: Sun, 1 Dec 2024 20:42:43 -0800 Subject: [PATCH 14/17] interfact support --- src/parser/class.js | 7 +- test/snapshot/__snapshots__/acid.test.js.snap | 2 + .../__snapshots__/attributes.test.js.snap | 8 + .../snapshot/__snapshots__/class.test.js.snap | 18 ++ .../classpropertyhooks.test.js.snap | 2 +- .../__snapshots__/comment.test.js.snap | 4 + .../__snapshots__/graceful.test.js.snap | 62 ++++++- .../__snapshots__/heredoc.test.js.snap | 2 + .../__snapshots__/interface.test.js.snap | 173 ++++++++++++++++++ .../__snapshots__/namespace.test.js.snap | 4 + .../__snapshots__/nowdoc.test.js.snap | 2 + .../__snapshots__/property.test.js.snap | 44 +++++ .../propertystatement.test.js.snap | 8 + test/snapshot/interface.test.js | 35 ++++ 14 files changed, 359 insertions(+), 12 deletions(-) diff --git a/src/parser/class.js b/src/parser/class.js index 042ef363f..86b8d9baf 100644 --- a/src/parser/class.js +++ b/src/parser/class.js @@ -213,7 +213,6 @@ module.exports = { // Property is using a hook to define getter/setters if (this.token === "{") { - this.next(); property_hooks = this.read_property_hooks(); } else { this.expect([";", ","]); @@ -252,6 +251,8 @@ module.exports = { if (this.version < 804) { this.raiseError("Parse Error: Typed Class Constants requires PHP 8.4+"); } + this.expect("{"); + this.next(); const hooks = []; @@ -548,9 +549,11 @@ module.exports = { this.next(); } attrs = []; + } else if (this.token === this.tok.T_STRING) { + result.push(this.read_variable_list(flags, attrs)); } else { // raise an error - this.error([this.tok.T_CONST, this.tok.T_FUNCTION]); + this.error([this.tok.T_CONST, this.tok.T_FUNCTION, this.tok.T_STRING]); this.next(); } } diff --git a/test/snapshot/__snapshots__/acid.test.js.snap b/test/snapshot/__snapshots__/acid.test.js.snap index d9a778fa6..35ebf3b4f 100644 --- a/test/snapshot/__snapshots__/acid.test.js.snap +++ b/test/snapshot/__snapshots__/acid.test.js.snap @@ -831,6 +831,8 @@ Program { "visibility": "", }, PropertyStatement { + "isAbstract": false, + "isFinal": false, "isStatic": false, "kind": "propertystatement", "loc": Location { diff --git a/test/snapshot/__snapshots__/attributes.test.js.snap b/test/snapshot/__snapshots__/attributes.test.js.snap index 45f3a43ff..0f1561e5c 100644 --- a/test/snapshot/__snapshots__/attributes.test.js.snap +++ b/test/snapshot/__snapshots__/attributes.test.js.snap @@ -365,6 +365,8 @@ Program { "attrGroups": [], "body": [ PropertyStatement { + "isAbstract": false, + "isFinal": false, "isStatic": false, "kind": "propertystatement", "properties": [ @@ -400,6 +402,8 @@ Program { "visibility": "public", }, PropertyStatement { + "isAbstract": false, + "isFinal": false, "isStatic": false, "kind": "propertystatement", "properties": [ @@ -435,6 +439,8 @@ Program { "visibility": "private", }, PropertyStatement { + "isAbstract": false, + "isFinal": false, "isStatic": false, "kind": "propertystatement", "properties": [ @@ -2569,6 +2575,8 @@ Program { "attrGroups": [], "body": [ PropertyStatement { + "isAbstract": false, + "isFinal": false, "isStatic": false, "kind": "propertystatement", "properties": [ diff --git a/test/snapshot/__snapshots__/class.test.js.snap b/test/snapshot/__snapshots__/class.test.js.snap index d40b49ef3..332597a64 100644 --- a/test/snapshot/__snapshots__/class.test.js.snap +++ b/test/snapshot/__snapshots__/class.test.js.snap @@ -40,6 +40,8 @@ Program { ], }, PropertyStatement { + "isAbstract": false, + "isFinal": false, "isStatic": false, "kind": "propertystatement", "leadingComments": [ @@ -462,6 +464,8 @@ Program { "attrGroups": [], "body": [ PropertyStatement { + "isAbstract": false, + "isFinal": false, "isStatic": false, "kind": "propertystatement", "properties": [ @@ -511,6 +515,8 @@ Program { "attrGroups": [], "body": [ PropertyStatement { + "isAbstract": false, + "isFinal": false, "isStatic": false, "kind": "propertystatement", "properties": [ @@ -538,6 +544,8 @@ Program { "visibility": "public", }, PropertyStatement { + "isAbstract": false, + "isFinal": false, "isStatic": true, "kind": "propertystatement", "properties": [ @@ -598,6 +606,8 @@ Program { "attrGroups": [], "body": [ PropertyStatement { + "isAbstract": false, + "isFinal": false, "isStatic": true, "kind": "propertystatement", "properties": [ @@ -1196,6 +1206,8 @@ Program { "visibility": "", }, PropertyStatement { + "isAbstract": false, + "isFinal": false, "isStatic": true, "kind": "propertystatement", "properties": [ @@ -1372,6 +1384,8 @@ Program { "visibility": "public", }, PropertyStatement { + "isAbstract": false, + "isFinal": false, "isStatic": false, "kind": "propertystatement", "properties": [ @@ -1392,6 +1406,8 @@ Program { "visibility": "protected", }, PropertyStatement { + "isAbstract": false, + "isFinal": false, "isStatic": false, "kind": "propertystatement", "properties": [ @@ -1769,6 +1785,8 @@ Program { "attrGroups": [], "body": [ PropertyStatement { + "isAbstract": false, + "isFinal": false, "isStatic": false, "kind": "propertystatement", "properties": [ diff --git a/test/snapshot/__snapshots__/classpropertyhooks.test.js.snap b/test/snapshot/__snapshots__/classpropertyhooks.test.js.snap index 4f3e43c43..2cbda3fe9 100644 --- a/test/snapshot/__snapshots__/classpropertyhooks.test.js.snap +++ b/test/snapshot/__snapshots__/classpropertyhooks.test.js.snap @@ -750,7 +750,7 @@ Program { } `; -exports[`classpropertyhooks not supported in php < 8.4 1`] = `"Parse Error: Typed Class Constants requires PHP 8.4+ on line 3"`; +exports[`classpropertyhooks not supported in php < 8.4 1`] = `"Parse Error: Typed Class Constants requires PHP 8.4+ on line 2"`; exports[`classpropertyhooks setter block form with explicit typed $value 1`] = ` Program { diff --git a/test/snapshot/__snapshots__/comment.test.js.snap b/test/snapshot/__snapshots__/comment.test.js.snap index ad0eae3d3..0607a397c 100644 --- a/test/snapshot/__snapshots__/comment.test.js.snap +++ b/test/snapshot/__snapshots__/comment.test.js.snap @@ -1846,6 +1846,8 @@ Program { "attrGroups": [], "body": [ PropertyStatement { + "isAbstract": false, + "isFinal": false, "isStatic": false, "kind": "propertystatement", "leadingComments": [ @@ -1892,6 +1894,8 @@ Program { "visibility": "protected", }, PropertyStatement { + "isAbstract": false, + "isFinal": false, "isStatic": true, "kind": "propertystatement", "leadingComments": [ diff --git a/test/snapshot/__snapshots__/graceful.test.js.snap b/test/snapshot/__snapshots__/graceful.test.js.snap index 286f3aaad..5b8abd4d7 100644 --- a/test/snapshot/__snapshots__/graceful.test.js.snap +++ b/test/snapshot/__snapshots__/graceful.test.js.snap @@ -5,7 +5,34 @@ Program { "children": [ Interface { "attrGroups": [], - "body": [], + "body": [ + PropertyStatement { + "isAbstract": false, + "isFinal": false, + "isStatic": false, + "kind": "propertystatement", + "properties": [ + Property { + "attrGroups": [], + "hooks": null, + "kind": "property", + "name": Identifier { + "kind": "identifier", + "name": "", + }, + "nullable": false, + "readonly": false, + "type": Name { + "kind": "name", + "name": "baz", + "resolution": "uqn", + }, + "value": null, + }, + ], + "visibility": "", + }, + ], "extends": null, "kind": "interface", "name": Identifier { @@ -22,25 +49,34 @@ Program { "message": "Parse Error : syntax error, unexpected 'implement' (T_STRING), expecting '{' on line 1", "token": "'implement' (T_STRING)", }, + Error { + "expected": 222, + "kind": "error", + "line": 1, + "message": "Parse Error : syntax error, unexpected '{', expecting T_VARIABLE on line 1", + "token": "'{'", + }, Error { "expected": [ - 198, - 182, + ",", + ";", + "=", + "{", ], "kind": "error", "line": 1, - "message": "Parse Error : syntax error, unexpected 'baz' (T_STRING) on line 1", - "token": "'baz' (T_STRING)", + "message": "Parse Error : syntax error, unexpected '}' on line 1", + "token": "'}'", }, Error { "expected": [ - 198, - 182, + ";", + ",", ], "kind": "error", "line": 1, - "message": "Parse Error : syntax error, unexpected '{' on line 1", - "token": "'{'", + "message": "Parse Error : syntax error, unexpected '}' on line 1", + "token": "'}'", }, ], "kind": "program", @@ -296,6 +332,8 @@ Program { "attrGroups": [], "body": [ PropertyStatement { + "isAbstract": false, + "isFinal": false, "isStatic": false, "kind": "propertystatement", "properties": [ @@ -320,6 +358,8 @@ Program { "visibility": "", }, PropertyStatement { + "isAbstract": false, + "isFinal": false, "isStatic": false, "kind": "propertystatement", "properties": [ @@ -886,6 +926,8 @@ Program { Trait { "body": [ PropertyStatement { + "isAbstract": false, + "isFinal": false, "isStatic": false, "kind": "propertystatement", "properties": [ @@ -910,6 +952,8 @@ Program { "visibility": "", }, PropertyStatement { + "isAbstract": false, + "isFinal": false, "isStatic": false, "kind": "propertystatement", "properties": [ diff --git a/test/snapshot/__snapshots__/heredoc.test.js.snap b/test/snapshot/__snapshots__/heredoc.test.js.snap index 1b07aba0e..0f1bed50b 100644 --- a/test/snapshot/__snapshots__/heredoc.test.js.snap +++ b/test/snapshot/__snapshots__/heredoc.test.js.snap @@ -1716,6 +1716,8 @@ FOOBAR", "visibility": "", }, PropertyStatement { + "isAbstract": false, + "isFinal": false, "isStatic": false, "kind": "propertystatement", "properties": [ diff --git a/test/snapshot/__snapshots__/interface.test.js.snap b/test/snapshot/__snapshots__/interface.test.js.snap index 14dd6a85c..577a7789a 100644 --- a/test/snapshot/__snapshots__/interface.test.js.snap +++ b/test/snapshot/__snapshots__/interface.test.js.snap @@ -125,3 +125,176 @@ Program { "kind": "program", } `; + +exports[`interface property hooks get + set 1`] = ` +Program { + "children": [ + Interface { + "attrGroups": [], + "body": [ + PropertyStatement { + "isAbstract": false, + "isFinal": false, + "isStatic": false, + "kind": "propertystatement", + "properties": [ + Property { + "attrGroups": [], + "hooks": [ + PropertyHook { + "body": null, + "byref": false, + "isFinal": false, + "kind": "propertyhook", + "name": "get", + "parameter": null, + }, + PropertyHook { + "body": null, + "byref": false, + "isFinal": false, + "kind": "propertyhook", + "name": "set", + "parameter": null, + }, + ], + "kind": "property", + "name": Identifier { + "kind": "identifier", + "name": "readable", + }, + "nullable": false, + "readonly": false, + "type": TypeReference { + "kind": "typereference", + "name": "int", + "raw": "int", + }, + "value": null, + }, + ], + "visibility": "public", + }, + ], + "extends": null, + "kind": "interface", + "name": Identifier { + "kind": "identifier", + "name": "I", + }, + }, + ], + "errors": [], + "kind": "program", +} +`; + +exports[`interface property hooks getter 1`] = ` +Program { + "children": [ + Interface { + "attrGroups": [], + "body": [ + PropertyStatement { + "isAbstract": false, + "isFinal": false, + "isStatic": false, + "kind": "propertystatement", + "properties": [ + Property { + "attrGroups": [], + "hooks": [ + PropertyHook { + "body": null, + "byref": false, + "isFinal": false, + "kind": "propertyhook", + "name": "get", + "parameter": null, + }, + ], + "kind": "property", + "name": Identifier { + "kind": "identifier", + "name": "readable", + }, + "nullable": false, + "readonly": false, + "type": TypeReference { + "kind": "typereference", + "name": "int", + "raw": "int", + }, + "value": null, + }, + ], + "visibility": "public", + }, + ], + "extends": null, + "kind": "interface", + "name": Identifier { + "kind": "identifier", + "name": "I", + }, + }, + ], + "errors": [], + "kind": "program", +} +`; + +exports[`interface property hooks setter 1`] = ` +Program { + "children": [ + Interface { + "attrGroups": [], + "body": [ + PropertyStatement { + "isAbstract": false, + "isFinal": false, + "isStatic": false, + "kind": "propertystatement", + "properties": [ + Property { + "attrGroups": [], + "hooks": [ + PropertyHook { + "body": null, + "byref": false, + "isFinal": false, + "kind": "propertyhook", + "name": "set", + "parameter": null, + }, + ], + "kind": "property", + "name": Identifier { + "kind": "identifier", + "name": "readable", + }, + "nullable": false, + "readonly": false, + "type": TypeReference { + "kind": "typereference", + "name": "int", + "raw": "int", + }, + "value": null, + }, + ], + "visibility": "public", + }, + ], + "extends": null, + "kind": "interface", + "name": Identifier { + "kind": "identifier", + "name": "I", + }, + }, + ], + "errors": [], + "kind": "program", +} +`; diff --git a/test/snapshot/__snapshots__/namespace.test.js.snap b/test/snapshot/__snapshots__/namespace.test.js.snap index 7caa9b786..8d1dc27f7 100644 --- a/test/snapshot/__snapshots__/namespace.test.js.snap +++ b/test/snapshot/__snapshots__/namespace.test.js.snap @@ -167,6 +167,8 @@ Program { "attrGroups": [], "body": [ PropertyStatement { + "isAbstract": false, + "isFinal": false, "isStatic": false, "kind": "propertystatement", "loc": Location { @@ -705,6 +707,8 @@ Program { "attrGroups": [], "body": [ PropertyStatement { + "isAbstract": false, + "isFinal": false, "isStatic": false, "kind": "propertystatement", "loc": Location { diff --git a/test/snapshot/__snapshots__/nowdoc.test.js.snap b/test/snapshot/__snapshots__/nowdoc.test.js.snap index 29b7dfdd4..efdda8986 100644 --- a/test/snapshot/__snapshots__/nowdoc.test.js.snap +++ b/test/snapshot/__snapshots__/nowdoc.test.js.snap @@ -223,6 +223,8 @@ FOOBAR", "visibility": "", }, PropertyStatement { + "isAbstract": false, + "isFinal": false, "isStatic": false, "kind": "propertystatement", "properties": [ diff --git a/test/snapshot/__snapshots__/property.test.js.snap b/test/snapshot/__snapshots__/property.test.js.snap index 748b70878..a2c0aaffc 100644 --- a/test/snapshot/__snapshots__/property.test.js.snap +++ b/test/snapshot/__snapshots__/property.test.js.snap @@ -7,6 +7,8 @@ Program { "attrGroups": [], "body": [ PropertyStatement { + "isAbstract": false, + "isFinal": false, "isStatic": false, "kind": "propertystatement", "properties": [ @@ -52,6 +54,8 @@ Program { "attrGroups": [], "body": [ PropertyStatement { + "isAbstract": false, + "isFinal": false, "isStatic": false, "kind": "propertystatement", "properties": [ @@ -100,6 +104,8 @@ Program { "attrGroups": [], "body": [ PropertyStatement { + "isAbstract": false, + "isFinal": false, "isStatic": false, "kind": "propertystatement", "properties": [ @@ -145,6 +151,8 @@ Program { "attrGroups": [], "body": [ PropertyStatement { + "isAbstract": false, + "isFinal": false, "isStatic": false, "kind": "propertystatement", "properties": [ @@ -193,6 +201,8 @@ Program { "attrGroups": [], "body": [ PropertyStatement { + "isAbstract": false, + "isFinal": false, "isStatic": false, "kind": "propertystatement", "properties": [ @@ -238,6 +248,8 @@ Program { "attrGroups": [], "body": [ PropertyStatement { + "isAbstract": false, + "isFinal": false, "isStatic": true, "kind": "propertystatement", "properties": [ @@ -283,6 +295,8 @@ Program { "attrGroups": [], "body": [ PropertyStatement { + "isAbstract": false, + "isFinal": false, "isStatic": true, "kind": "propertystatement", "properties": [ @@ -331,6 +345,8 @@ Program { "attrGroups": [], "body": [ PropertyStatement { + "isAbstract": false, + "isFinal": false, "isStatic": false, "kind": "propertystatement", "properties": [ @@ -379,6 +395,8 @@ Program { "attrGroups": [], "body": [ PropertyStatement { + "isAbstract": false, + "isFinal": false, "isStatic": false, "kind": "propertystatement", "properties": [ @@ -424,6 +442,8 @@ Program { "attrGroups": [], "body": [ PropertyStatement { + "isAbstract": false, + "isFinal": false, "isStatic": false, "kind": "propertystatement", "properties": [ @@ -472,6 +492,8 @@ Program { "attrGroups": [], "body": [ PropertyStatement { + "isAbstract": false, + "isFinal": false, "isStatic": false, "kind": "propertystatement", "properties": [ @@ -544,6 +566,8 @@ Program { "attrGroups": [], "body": [ PropertyStatement { + "isAbstract": false, + "isFinal": false, "isStatic": false, "kind": "propertystatement", "properties": [ @@ -606,6 +630,8 @@ Program { "attrGroups": [], "body": [ PropertyStatement { + "isAbstract": false, + "isFinal": false, "isStatic": false, "kind": "propertystatement", "properties": [ @@ -662,6 +688,8 @@ Program { "attrGroups": [], "body": [ PropertyStatement { + "isAbstract": false, + "isFinal": false, "isStatic": false, "kind": "propertystatement", "properties": [ @@ -711,6 +739,8 @@ Program { "attrGroups": [], "body": [ PropertyStatement { + "isAbstract": false, + "isFinal": false, "isStatic": false, "kind": "propertystatement", "properties": [ @@ -760,6 +790,8 @@ Program { "attrGroups": [], "body": [ PropertyStatement { + "isAbstract": false, + "isFinal": false, "isStatic": false, "kind": "propertystatement", "properties": [ @@ -811,6 +843,8 @@ Program { "attrGroups": [], "body": [ PropertyStatement { + "isAbstract": false, + "isFinal": false, "isStatic": false, "kind": "propertystatement", "properties": [ @@ -878,6 +912,8 @@ Program { "attrGroups": [], "body": [ PropertyStatement { + "isAbstract": false, + "isFinal": false, "isStatic": false, "kind": "propertystatement", "properties": [ @@ -930,6 +966,8 @@ Program { "attrGroups": [], "body": [ PropertyStatement { + "isAbstract": false, + "isFinal": false, "isStatic": false, "kind": "propertystatement", "properties": [ @@ -1002,6 +1040,8 @@ Program { "attrGroups": [], "body": [ PropertyStatement { + "isAbstract": false, + "isFinal": false, "isStatic": false, "kind": "propertystatement", "properties": [ @@ -1053,6 +1093,8 @@ Program { "attrGroups": [], "body": [ PropertyStatement { + "isAbstract": false, + "isFinal": false, "isStatic": false, "kind": "propertystatement", "properties": [ @@ -1101,6 +1143,8 @@ Program { "attrGroups": [], "body": [ PropertyStatement { + "isAbstract": false, + "isFinal": false, "isStatic": false, "kind": "propertystatement", "properties": [ diff --git a/test/snapshot/__snapshots__/propertystatement.test.js.snap b/test/snapshot/__snapshots__/propertystatement.test.js.snap index 585066186..2d90f4e2a 100644 --- a/test/snapshot/__snapshots__/propertystatement.test.js.snap +++ b/test/snapshot/__snapshots__/propertystatement.test.js.snap @@ -7,6 +7,8 @@ Program { "attrGroups": [], "body": [ PropertyStatement { + "isAbstract": false, + "isFinal": false, "isStatic": false, "kind": "propertystatement", "properties": [ @@ -78,6 +80,8 @@ Program { "attrGroups": [], "body": [ PropertyStatement { + "isAbstract": false, + "isFinal": false, "isStatic": false, "kind": "propertystatement", "properties": [ @@ -149,6 +153,8 @@ Program { "attrGroups": [], "body": [ PropertyStatement { + "isAbstract": false, + "isFinal": false, "isStatic": false, "kind": "propertystatement", "properties": [ @@ -194,6 +200,8 @@ Program { "attrGroups": [], "body": [ PropertyStatement { + "isAbstract": false, + "isFinal": false, "isStatic": false, "kind": "propertystatement", "properties": [ diff --git a/test/snapshot/interface.test.js b/test/snapshot/interface.test.js index 0c52595b4..2ebb2d240 100644 --- a/test/snapshot/interface.test.js +++ b/test/snapshot/interface.test.js @@ -17,4 +17,39 @@ describe("interface", function () { }), ).toMatchSnapshot(); }); + + describe("property hooks", function () { + const test_parser = parser.create({ + parser: { + version: "8.4", + }, + }); + + it("getter", () => { + const code = `interface I { + // An implementing class MUST have a publicly-readable property, + // but whether or not it's publicly settable is unrestricted. + public int $readable { get; } +}`; + expect(test_parser.parseEval(code)).toMatchSnapshot(); + }); + + it("setter", () => { + const code = `interface I { + // An implementing class MUST have a publicly-readable property, + // but whether or not it's publicly settable is unrestricted. + public int $readable { set; } +}`; + expect(test_parser.parseEval(code)).toMatchSnapshot(); + }); + + it("get + set", () => { + const code = `interface I { + // An implementing class MUST have a publicly-readable property, + // but whether or not it's publicly settable is unrestricted. + public int $readable { get; set;} +}`; + expect(test_parser.parseEval(code)).toMatchSnapshot(); + }); + }); }); From 6b746853a22930dd2e1b372b6db4e80466497da9 Mon Sep 17 00:00:00 2001 From: Thomas Genin Date: Sun, 1 Dec 2024 20:48:48 -0800 Subject: [PATCH 15/17] types --- types.d.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/types.d.ts b/types.d.ts index a5ad9c93f..4386c2b19 100644 --- a/types.d.ts +++ b/types.d.ts @@ -777,6 +777,16 @@ declare module "php-parser" { type: Identifier | Identifier[] | null; attrGroups: AttrGroup[]; } + /** + * Defines a class property hook getter & setter + */ + class PropertyHook extends Statement { + name: string; + isFinal: boolean; + byref: boolean; + parameter: Parameter | null; + body: Block | Statement; + } /** * Lookup to an object property */ From 55ea8e5fd8d2f5bee4a7cd0ea5d167a668185fcd Mon Sep 17 00:00:00 2001 From: Thomas Genin Date: Sun, 1 Dec 2024 21:53:49 -0800 Subject: [PATCH 16/17] Extend node instead of statement --- src/ast/propertyhook.js | 8 ++++---- types.d.ts | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/ast/propertyhook.js b/src/ast/propertyhook.js index a96282529..8ff8a4ea2 100644 --- a/src/ast/propertyhook.js +++ b/src/ast/propertyhook.js @@ -5,7 +5,7 @@ */ "use strict"; -const Statement = require("./statement"); +const Node = require("./node"); const KIND = "propertyhook"; /** @@ -13,17 +13,17 @@ const KIND = "propertyhook"; * * @constructor PropertyHook * @memberOf module:php-parser - * @extends {Statement} + * @extends {Node} * @property {string} name * @property {Boolean} isFinal * @property {Boolean} byref * @property {Parameter|null} parameter * @property {Block|Statement} body */ -module.exports = Statement.extends( +module.exports = Node.extends( KIND, function PropertyHook(name, isFinal, byref, parameter, body, docs, location) { - Statement.apply(this, [KIND, docs, location]); + Node.apply(this, [KIND, docs, location]); this.name = name; this.byref = byref; this.parameter = parameter; diff --git a/types.d.ts b/types.d.ts index 4386c2b19..bc91586d9 100644 --- a/types.d.ts +++ b/types.d.ts @@ -780,7 +780,7 @@ declare module "php-parser" { /** * Defines a class property hook getter & setter */ - class PropertyHook extends Statement { + class PropertyHook extends Node { name: string; isFinal: boolean; byref: boolean; From 1c46c8d941b154f84ec5acfd066ed75a7790e4a4 Mon Sep 17 00:00:00 2001 From: Thomas Genin Date: Tue, 3 Dec 2024 19:24:30 -0800 Subject: [PATCH 17/17] hooks default to array --- src/ast/property.js | 2 +- src/parser/class.js | 4 +- test/snapshot/__snapshots__/acid.test.js.snap | 2 +- .../__snapshots__/attributes.test.js.snap | 8 ++-- .../snapshot/__snapshots__/class.test.js.snap | 18 ++++---- .../classpropertyhooks.test.js.snap | 8 ++-- .../__snapshots__/comment.test.js.snap | 6 +-- .../__snapshots__/graceful.test.js.snap | 10 ++--- .../__snapshots__/heredoc.test.js.snap | 2 +- .../__snapshots__/namespace.test.js.snap | 4 +- .../__snapshots__/nowdoc.test.js.snap | 2 +- .../__snapshots__/property.test.js.snap | 44 +++++++++---------- .../propertystatement.test.js.snap | 16 +++---- 13 files changed, 63 insertions(+), 63 deletions(-) diff --git a/src/ast/property.js b/src/ast/property.js index afcc4e42d..8a03219a2 100644 --- a/src/ast/property.js +++ b/src/ast/property.js @@ -18,7 +18,7 @@ const KIND = "property"; * @property {boolean} readonly * @property {boolean} nullable * @property {Identifier|Array|null} type - * @propert {PropertyHook[]|null} hooks + * @propert {PropertyHook[]} hooks * @property {AttrGroup[]} attrGroups */ module.exports = Statement.extends( diff --git a/src/parser/class.js b/src/parser/class.js index 86b8d9baf..4d086c1a5 100644 --- a/src/parser/class.js +++ b/src/parser/class.js @@ -201,7 +201,7 @@ module.exports = { propName = propName(name); let value = null; - let property_hooks = null; + let property_hooks = []; this.expect([",", ";", "=", "{"]); @@ -264,7 +264,7 @@ module.exports = { this.next(); return hooks; } - return null; + return []; }, read_property_hook: function () { diff --git a/test/snapshot/__snapshots__/acid.test.js.snap b/test/snapshot/__snapshots__/acid.test.js.snap index 35ebf3b4f..6a5294025 100644 --- a/test/snapshot/__snapshots__/acid.test.js.snap +++ b/test/snapshot/__snapshots__/acid.test.js.snap @@ -854,7 +854,7 @@ Program { "properties": [ Property { "attrGroups": [], - "hooks": null, + "hooks": [], "kind": "property", "loc": Location { "end": Position { diff --git a/test/snapshot/__snapshots__/attributes.test.js.snap b/test/snapshot/__snapshots__/attributes.test.js.snap index 0f1561e5c..8dd279b4a 100644 --- a/test/snapshot/__snapshots__/attributes.test.js.snap +++ b/test/snapshot/__snapshots__/attributes.test.js.snap @@ -383,7 +383,7 @@ Program { "kind": "attrgroup", }, ], - "hooks": null, + "hooks": [], "kind": "property", "name": Identifier { "kind": "identifier", @@ -420,7 +420,7 @@ Program { "kind": "attrgroup", }, ], - "hooks": null, + "hooks": [], "kind": "property", "name": Identifier { "kind": "identifier", @@ -457,7 +457,7 @@ Program { "kind": "attrgroup", }, ], - "hooks": null, + "hooks": [], "kind": "property", "name": Identifier { "kind": "identifier", @@ -2663,7 +2663,7 @@ Program { "kind": "attrgroup", }, ], - "hooks": null, + "hooks": [], "kind": "property", "name": Identifier { "kind": "identifier", diff --git a/test/snapshot/__snapshots__/class.test.js.snap b/test/snapshot/__snapshots__/class.test.js.snap index 332597a64..67bb7f6c0 100644 --- a/test/snapshot/__snapshots__/class.test.js.snap +++ b/test/snapshot/__snapshots__/class.test.js.snap @@ -68,7 +68,7 @@ Program { "properties": [ Property { "attrGroups": [], - "hooks": null, + "hooks": [], "kind": "property", "name": Identifier { "kind": "identifier", @@ -471,7 +471,7 @@ Program { "properties": [ Property { "attrGroups": [], - "hooks": null, + "hooks": [], "kind": "property", "name": Identifier { "kind": "identifier", @@ -522,7 +522,7 @@ Program { "properties": [ Property { "attrGroups": [], - "hooks": null, + "hooks": [], "kind": "property", "name": Identifier { "kind": "identifier", @@ -551,7 +551,7 @@ Program { "properties": [ Property { "attrGroups": [], - "hooks": null, + "hooks": [], "kind": "property", "name": Identifier { "kind": "identifier", @@ -613,7 +613,7 @@ Program { "properties": [ Property { "attrGroups": [], - "hooks": null, + "hooks": [], "kind": "property", "name": Identifier { "kind": "identifier", @@ -1213,7 +1213,7 @@ Program { "properties": [ Property { "attrGroups": [], - "hooks": null, + "hooks": [], "kind": "property", "name": Identifier { "kind": "identifier", @@ -1391,7 +1391,7 @@ Program { "properties": [ Property { "attrGroups": [], - "hooks": null, + "hooks": [], "kind": "property", "name": Identifier { "kind": "identifier", @@ -1413,7 +1413,7 @@ Program { "properties": [ Property { "attrGroups": [], - "hooks": null, + "hooks": [], "kind": "property", "name": Identifier { "kind": "identifier", @@ -1792,7 +1792,7 @@ Program { "properties": [ Property { "attrGroups": [], - "hooks": null, + "hooks": [], "kind": "property", "name": Identifier { "kind": "identifier", diff --git a/test/snapshot/__snapshots__/classpropertyhooks.test.js.snap b/test/snapshot/__snapshots__/classpropertyhooks.test.js.snap index 2cbda3fe9..a28e33092 100644 --- a/test/snapshot/__snapshots__/classpropertyhooks.test.js.snap +++ b/test/snapshot/__snapshots__/classpropertyhooks.test.js.snap @@ -202,7 +202,7 @@ Program { "properties": [ Property { "attrGroups": [], - "hooks": null, + "hooks": [], "kind": "property", "name": Identifier { "kind": "identifier", @@ -492,7 +492,7 @@ Program { "properties": [ Property { "attrGroups": [], - "hooks": null, + "hooks": [], "kind": "property", "name": Identifier { "kind": "identifier", @@ -1546,7 +1546,7 @@ Program { "properties": [ Property { "attrGroups": [], - "hooks": null, + "hooks": [], "kind": "property", "name": Identifier { "kind": "identifier", @@ -1775,7 +1775,7 @@ Program { "properties": [ Property { "attrGroups": [], - "hooks": null, + "hooks": [], "kind": "property", "name": Identifier { "kind": "identifier", diff --git a/test/snapshot/__snapshots__/comment.test.js.snap b/test/snapshot/__snapshots__/comment.test.js.snap index 0607a397c..507ca0338 100644 --- a/test/snapshot/__snapshots__/comment.test.js.snap +++ b/test/snapshot/__snapshots__/comment.test.js.snap @@ -1866,7 +1866,7 @@ Program { "properties": [ Property { "attrGroups": [], - "hooks": null, + "hooks": [], "kind": "property", "name": Identifier { "kind": "identifier", @@ -1879,7 +1879,7 @@ Program { }, Property { "attrGroups": [], - "hooks": null, + "hooks": [], "kind": "property", "name": Identifier { "kind": "identifier", @@ -1914,7 +1914,7 @@ Program { "properties": [ Property { "attrGroups": [], - "hooks": null, + "hooks": [], "kind": "property", "name": Identifier { "kind": "identifier", diff --git a/test/snapshot/__snapshots__/graceful.test.js.snap b/test/snapshot/__snapshots__/graceful.test.js.snap index 5b8abd4d7..bdbda8496 100644 --- a/test/snapshot/__snapshots__/graceful.test.js.snap +++ b/test/snapshot/__snapshots__/graceful.test.js.snap @@ -14,7 +14,7 @@ Program { "properties": [ Property { "attrGroups": [], - "hooks": null, + "hooks": [], "kind": "property", "name": Identifier { "kind": "identifier", @@ -339,7 +339,7 @@ Program { "properties": [ Property { "attrGroups": [], - "hooks": null, + "hooks": [], "kind": "property", "name": Identifier { "kind": "identifier", @@ -365,7 +365,7 @@ Program { "properties": [ Property { "attrGroups": [], - "hooks": null, + "hooks": [], "kind": "property", "name": Identifier { "kind": "identifier", @@ -933,7 +933,7 @@ Program { "properties": [ Property { "attrGroups": [], - "hooks": null, + "hooks": [], "kind": "property", "name": Identifier { "kind": "identifier", @@ -959,7 +959,7 @@ Program { "properties": [ Property { "attrGroups": [], - "hooks": null, + "hooks": [], "kind": "property", "name": Identifier { "kind": "identifier", diff --git a/test/snapshot/__snapshots__/heredoc.test.js.snap b/test/snapshot/__snapshots__/heredoc.test.js.snap index 0f1bed50b..7a9b38fcd 100644 --- a/test/snapshot/__snapshots__/heredoc.test.js.snap +++ b/test/snapshot/__snapshots__/heredoc.test.js.snap @@ -1723,7 +1723,7 @@ FOOBAR", "properties": [ Property { "attrGroups": [], - "hooks": null, + "hooks": [], "kind": "property", "name": Identifier { "kind": "identifier", diff --git a/test/snapshot/__snapshots__/namespace.test.js.snap b/test/snapshot/__snapshots__/namespace.test.js.snap index 8d1dc27f7..ead6f0d16 100644 --- a/test/snapshot/__snapshots__/namespace.test.js.snap +++ b/test/snapshot/__snapshots__/namespace.test.js.snap @@ -187,7 +187,7 @@ Program { "properties": [ Property { "attrGroups": [], - "hooks": null, + "hooks": [], "kind": "property", "loc": Location { "end": Position { @@ -727,7 +727,7 @@ Program { "properties": [ Property { "attrGroups": [], - "hooks": null, + "hooks": [], "kind": "property", "loc": Location { "end": Position { diff --git a/test/snapshot/__snapshots__/nowdoc.test.js.snap b/test/snapshot/__snapshots__/nowdoc.test.js.snap index efdda8986..51d7e5363 100644 --- a/test/snapshot/__snapshots__/nowdoc.test.js.snap +++ b/test/snapshot/__snapshots__/nowdoc.test.js.snap @@ -230,7 +230,7 @@ FOOBAR", "properties": [ Property { "attrGroups": [], - "hooks": null, + "hooks": [], "kind": "property", "name": Identifier { "kind": "identifier", diff --git a/test/snapshot/__snapshots__/property.test.js.snap b/test/snapshot/__snapshots__/property.test.js.snap index a2c0aaffc..398f41fa4 100644 --- a/test/snapshot/__snapshots__/property.test.js.snap +++ b/test/snapshot/__snapshots__/property.test.js.snap @@ -14,7 +14,7 @@ Program { "properties": [ Property { "attrGroups": [], - "hooks": null, + "hooks": [], "kind": "property", "name": Identifier { "kind": "identifier", @@ -61,7 +61,7 @@ Program { "properties": [ Property { "attrGroups": [], - "hooks": null, + "hooks": [], "kind": "property", "name": Identifier { "kind": "identifier", @@ -111,7 +111,7 @@ Program { "properties": [ Property { "attrGroups": [], - "hooks": null, + "hooks": [], "kind": "property", "name": Identifier { "kind": "identifier", @@ -158,7 +158,7 @@ Program { "properties": [ Property { "attrGroups": [], - "hooks": null, + "hooks": [], "kind": "property", "name": Identifier { "kind": "identifier", @@ -208,7 +208,7 @@ Program { "properties": [ Property { "attrGroups": [], - "hooks": null, + "hooks": [], "kind": "property", "name": Identifier { "kind": "identifier", @@ -255,7 +255,7 @@ Program { "properties": [ Property { "attrGroups": [], - "hooks": null, + "hooks": [], "kind": "property", "name": Identifier { "kind": "identifier", @@ -302,7 +302,7 @@ Program { "properties": [ Property { "attrGroups": [], - "hooks": null, + "hooks": [], "kind": "property", "name": Identifier { "kind": "identifier", @@ -352,7 +352,7 @@ Program { "properties": [ Property { "attrGroups": [], - "hooks": null, + "hooks": [], "kind": "property", "name": Identifier { "kind": "identifier", @@ -402,7 +402,7 @@ Program { "properties": [ Property { "attrGroups": [], - "hooks": null, + "hooks": [], "kind": "property", "name": Identifier { "kind": "identifier", @@ -449,7 +449,7 @@ Program { "properties": [ Property { "attrGroups": [], - "hooks": null, + "hooks": [], "kind": "property", "name": Identifier { "kind": "identifier", @@ -499,7 +499,7 @@ Program { "properties": [ Property { "attrGroups": [], - "hooks": null, + "hooks": [], "kind": "property", "name": Identifier { "kind": "identifier", @@ -573,7 +573,7 @@ Program { "properties": [ Property { "attrGroups": [], - "hooks": null, + "hooks": [], "kind": "property", "name": Identifier { "kind": "identifier", @@ -637,7 +637,7 @@ Program { "properties": [ Property { "attrGroups": [], - "hooks": null, + "hooks": [], "kind": "property", "name": Identifier { "kind": "identifier", @@ -695,7 +695,7 @@ Program { "properties": [ Property { "attrGroups": [], - "hooks": null, + "hooks": [], "kind": "property", "name": Identifier { "kind": "identifier", @@ -746,7 +746,7 @@ Program { "properties": [ Property { "attrGroups": [], - "hooks": null, + "hooks": [], "kind": "property", "name": Identifier { "kind": "identifier", @@ -797,7 +797,7 @@ Program { "properties": [ Property { "attrGroups": [], - "hooks": null, + "hooks": [], "kind": "property", "name": Identifier { "kind": "identifier", @@ -850,7 +850,7 @@ Program { "properties": [ Property { "attrGroups": [], - "hooks": null, + "hooks": [], "kind": "property", "name": Identifier { "kind": "identifier", @@ -919,7 +919,7 @@ Program { "properties": [ Property { "attrGroups": [], - "hooks": null, + "hooks": [], "kind": "property", "name": Identifier { "kind": "identifier", @@ -973,7 +973,7 @@ Program { "properties": [ Property { "attrGroups": [], - "hooks": null, + "hooks": [], "kind": "property", "name": Identifier { "kind": "identifier", @@ -1047,7 +1047,7 @@ Program { "properties": [ Property { "attrGroups": [], - "hooks": null, + "hooks": [], "kind": "property", "name": Identifier { "kind": "identifier", @@ -1100,7 +1100,7 @@ Program { "properties": [ Property { "attrGroups": [], - "hooks": null, + "hooks": [], "kind": "property", "name": Identifier { "kind": "identifier", @@ -1150,7 +1150,7 @@ Program { "properties": [ Property { "attrGroups": [], - "hooks": null, + "hooks": [], "kind": "property", "name": Identifier { "kind": "identifier", diff --git a/test/snapshot/__snapshots__/propertystatement.test.js.snap b/test/snapshot/__snapshots__/propertystatement.test.js.snap index 2d90f4e2a..aa5dbba4d 100644 --- a/test/snapshot/__snapshots__/propertystatement.test.js.snap +++ b/test/snapshot/__snapshots__/propertystatement.test.js.snap @@ -14,7 +14,7 @@ Program { "properties": [ Property { "attrGroups": [], - "hooks": null, + "hooks": [], "kind": "property", "name": Identifier { "kind": "identifier", @@ -27,7 +27,7 @@ Program { }, Property { "attrGroups": [], - "hooks": null, + "hooks": [], "kind": "property", "name": Identifier { "kind": "identifier", @@ -40,7 +40,7 @@ Program { }, Property { "attrGroups": [], - "hooks": null, + "hooks": [], "kind": "property", "name": Identifier { "kind": "identifier", @@ -87,7 +87,7 @@ Program { "properties": [ Property { "attrGroups": [], - "hooks": null, + "hooks": [], "kind": "property", "name": Identifier { "kind": "identifier", @@ -100,7 +100,7 @@ Program { }, Property { "attrGroups": [], - "hooks": null, + "hooks": [], "kind": "property", "name": Identifier { "kind": "identifier", @@ -113,7 +113,7 @@ Program { }, Property { "attrGroups": [], - "hooks": null, + "hooks": [], "kind": "property", "name": Identifier { "kind": "identifier", @@ -160,7 +160,7 @@ Program { "properties": [ Property { "attrGroups": [], - "hooks": null, + "hooks": [], "kind": "property", "name": Identifier { "kind": "identifier", @@ -207,7 +207,7 @@ Program { "properties": [ Property { "attrGroups": [], - "hooks": null, + "hooks": [], "kind": "property", "name": Identifier { "kind": "identifier",