From 10678ecba8420733cc97914536cecf6327105304 Mon Sep 17 00:00:00 2001 From: Jorg Sowa Date: Tue, 24 Mar 2026 01:26:34 +0100 Subject: [PATCH] fix: correct loc.start for classconstant and propertystatement with attributes --- src/parser/class.js | 38 +- test/snapshot/__snapshots__/acid.test.js.snap | 6 +- .../__snapshots__/location.test.js.snap | 549 +++++++++++++++++- .../__snapshots__/namespace.test.js.snap | 8 +- test/snapshot/location.test.js | 8 + 5 files changed, 590 insertions(+), 19 deletions(-) diff --git a/src/parser/class.js b/src/parser/class.js index be8824482..254bad8ec 100644 --- a/src/parser/class.js +++ b/src/parser/class.js @@ -93,6 +93,8 @@ module.exports = { continue; } + const locStart = this.position(); + if (this.token === this.tok.T_ATTRIBUTE) { attrs = this.read_attr_list(); } @@ -108,14 +110,12 @@ module.exports = { continue; } - const locStart = this.position(); - // read member flags const flags = this.read_member_flags(false); // check constant if (this.token === this.tok.T_CONST) { - const constants = this.read_constant_list(flags, attrs); + const constants = this.read_constant_list(flags, attrs, locStart); if (this.expect(";")) { this.next(); } @@ -151,7 +151,7 @@ module.exports = { this.token === this.tok.T_STRING))) ) { // reads a variable - const variables = this.read_variable_list(flags, attrs); + const variables = this.read_variable_list(flags, attrs, locStart); attrs = []; result = result.concat(variables); } else { @@ -176,7 +176,7 @@ module.exports = { * variable_list ::= (variable_declaration ',')* variable_declaration * ``` */ - read_variable_list(flags, attrs) { + read_variable_list(flags, attrs, locStart) { let property_statement = this.node("propertystatement"); const properties = this.read_list( @@ -233,6 +233,16 @@ module.exports = { ); property_statement = property_statement(null, properties, flags); + if (locStart && property_statement.loc) { + property_statement.loc.start = locStart; + if (property_statement.loc.source) { + property_statement.loc.source = this.lexer._input.substr( + property_statement.loc.start.offset, + property_statement.loc.end.offset - + property_statement.loc.start.offset, + ); + } + } // semicolons are found only for regular properties definitions. // Property hooks are terminated by a closing curly brace, }. @@ -335,7 +345,7 @@ module.exports = { * constant_list ::= T_CONST [type] (constant_declaration ',')* constant_declaration * ``` */ - read_constant_list(flags, attrs) { + read_constant_list(flags, attrs, locStart) { const result = this.node("classconstant"); if (this.expect(this.tok.T_CONST)) { this.next(); @@ -383,7 +393,17 @@ module.exports = { ",", ); - return result(null, items, flags, nullable, type, attrs || []); + const node = result(null, items, flags, nullable, type, attrs || []); + if (locStart && node.loc) { + node.loc.start = locStart; + if (node.loc.source) { + node.loc.source = this.lexer._input.substr( + node.loc.start.offset, + node.loc.end.offset - node.loc.start.offset, + ); + } + } + return node; }, /* * Read member flags @@ -581,7 +601,7 @@ module.exports = { // check constant if (this.token === this.tok.T_CONST) { - const constants = this.read_constant_list(flags, attrs); + const constants = this.read_constant_list(flags, attrs, locStart); if (this.expect(";")) { this.next(); } @@ -600,7 +620,7 @@ module.exports = { this.next(); } } else if (this.token === this.tok.T_STRING) { - result.push(this.read_variable_list(flags, attrs)); + result.push(this.read_variable_list(flags, attrs, locStart)); } else { // raise an error this.error([this.tok.T_CONST, this.tok.T_FUNCTION, this.tok.T_STRING]); diff --git a/test/snapshot/__snapshots__/acid.test.js.snap b/test/snapshot/__snapshots__/acid.test.js.snap index dcf6bf98c..8ea7eb6a3 100644 --- a/test/snapshot/__snapshots__/acid.test.js.snap +++ b/test/snapshot/__snapshots__/acid.test.js.snap @@ -841,14 +841,14 @@ Program { "line": 35, "offset": 648, }, - "source": "$dwarf = [ + "source": "protected $dwarf = [ 'sneezy' => 'achoum', 'bashful' => 'tadah' ]", "start": Position { - "column": 14, + "column": 4, "line": 32, - "offset": 577, + "offset": 567, }, }, "properties": [ diff --git a/test/snapshot/__snapshots__/location.test.js.snap b/test/snapshot/__snapshots__/location.test.js.snap index 0ac220d4e..c872f0580 100644 --- a/test/snapshot/__snapshots__/location.test.js.snap +++ b/test/snapshot/__snapshots__/location.test.js.snap @@ -3838,11 +3838,11 @@ Program { "line": 1, "offset": 32, }, - "source": "const BAR = 1", + "source": "public const BAR = 1", "start": Position { - "column": 19, + "column": 12, "line": 1, - "offset": 19, + "offset": 12, }, }, "nullable": false, @@ -4044,6 +4044,294 @@ Program { } `; +exports[`Test locations test classconstant attribute loc includes attrgroup 1`] = ` +Program { + "children": [ + Class { + "attrGroups": [], + "body": [ + ClassConstant { + "attrGroups": [ + AttrGroup { + "attrs": [ + Attribute { + "args": [], + "kind": "attribute", + "loc": Location { + "end": Position { + "column": 22, + "line": 1, + "offset": 22, + }, + "source": "Deprecated", + "start": Position { + "column": 12, + "line": 1, + "offset": 12, + }, + }, + "name": "Deprecated", + }, + ], + "kind": "attrgroup", + "loc": Location { + "end": Position { + "column": 23, + "line": 1, + "offset": 23, + }, + "source": "#[Deprecated]", + "start": Position { + "column": 10, + "line": 1, + "offset": 10, + }, + }, + }, + ], + "constants": [ + Constant { + "kind": "constant", + "loc": Location { + "end": Position { + "column": 44, + "line": 1, + "offset": 44, + }, + "source": "FOO = 1", + "start": Position { + "column": 37, + "line": 1, + "offset": 37, + }, + }, + "name": Identifier { + "kind": "identifier", + "loc": Location { + "end": Position { + "column": 40, + "line": 1, + "offset": 40, + }, + "source": "FOO", + "start": Position { + "column": 37, + "line": 1, + "offset": 37, + }, + }, + "name": "FOO", + }, + "value": Number { + "kind": "number", + "loc": Location { + "end": Position { + "column": 44, + "line": 1, + "offset": 44, + }, + "source": "1", + "start": Position { + "column": 43, + "line": 1, + "offset": 43, + }, + }, + "value": "1", + }, + }, + ], + "final": false, + "kind": "classconstant", + "loc": Location { + "end": Position { + "column": 44, + "line": 1, + "offset": 44, + }, + "source": "#[Deprecated] public const FOO = 1", + "start": Position { + "column": 10, + "line": 1, + "offset": 10, + }, + }, + "nullable": false, + "type": null, + "visibility": "public", + }, + PropertyStatement { + "isAbstract": false, + "isFinal": false, + "isStatic": false, + "kind": "propertystatement", + "loc": Location { + "end": Position { + "column": 61, + "line": 1, + "offset": 61, + }, + "source": "public $bar = 2", + "start": Position { + "column": 46, + "line": 1, + "offset": 46, + }, + }, + "properties": [ + Property { + "attrGroups": [ + AttrGroup { + "attrs": [ + Attribute { + "args": [], + "kind": "attribute", + "loc": Location { + "end": Position { + "column": 22, + "line": 1, + "offset": 22, + }, + "source": "Deprecated", + "start": Position { + "column": 12, + "line": 1, + "offset": 12, + }, + }, + "name": "Deprecated", + }, + ], + "kind": "attrgroup", + "loc": Location { + "end": Position { + "column": 23, + "line": 1, + "offset": 23, + }, + "source": "#[Deprecated]", + "start": Position { + "column": 10, + "line": 1, + "offset": 10, + }, + }, + }, + ], + "hooks": [], + "kind": "property", + "loc": Location { + "end": Position { + "column": 61, + "line": 1, + "offset": 61, + }, + "source": "$bar = 2", + "start": Position { + "column": 53, + "line": 1, + "offset": 53, + }, + }, + "name": Identifier { + "kind": "identifier", + "loc": Location { + "end": Position { + "column": 57, + "line": 1, + "offset": 57, + }, + "source": "$bar", + "start": Position { + "column": 53, + "line": 1, + "offset": 53, + }, + }, + "name": "bar", + }, + "nullable": false, + "readonly": false, + "type": null, + "value": Number { + "kind": "number", + "loc": Location { + "end": Position { + "column": 61, + "line": 1, + "offset": 61, + }, + "source": "2", + "start": Position { + "column": 60, + "line": 1, + "offset": 60, + }, + }, + "value": "2", + }, + }, + ], + "visibility": "public", + }, + ], + "extends": null, + "implements": null, + "isAbstract": false, + "isAnonymous": false, + "isFinal": false, + "isReadonly": false, + "kind": "class", + "loc": Location { + "end": Position { + "column": 64, + "line": 1, + "offset": 64, + }, + "source": "class C { #[Deprecated] public const FOO = 1; public $bar = 2; }", + "start": Position { + "column": 0, + "line": 1, + "offset": 0, + }, + }, + "name": Identifier { + "kind": "identifier", + "loc": Location { + "end": Position { + "column": 7, + "line": 1, + "offset": 7, + }, + "source": "C", + "start": Position { + "column": 6, + "line": 1, + "offset": 6, + }, + }, + "name": "C", + }, + }, + ], + "errors": [], + "kind": "program", + "loc": Location { + "end": Position { + "column": 64, + "line": 1, + "offset": 64, + }, + "source": "class C { #[Deprecated] public const FOO = 1; public $bar = 2; }", + "start": Position { + "column": 0, + "line": 1, + "offset": 0, + }, + }, +} +`; + exports[`Test locations test clone 1`] = ` Program { "children": [ @@ -11712,6 +12000,261 @@ Program { } `; +exports[`Test locations test propertystatement attribute loc includes attrgroup 1`] = ` +Program { + "children": [ + Class { + "attrGroups": [], + "body": [ + PropertyStatement { + "isAbstract": false, + "isFinal": false, + "isStatic": false, + "kind": "propertystatement", + "loc": Location { + "end": Position { + "column": 36, + "line": 1, + "offset": 36, + }, + "source": "#[Pure] protected $foo = 1", + "start": Position { + "column": 10, + "line": 1, + "offset": 10, + }, + }, + "properties": [ + Property { + "attrGroups": [ + AttrGroup { + "attrs": [ + Attribute { + "args": [], + "kind": "attribute", + "loc": Location { + "end": Position { + "column": 16, + "line": 1, + "offset": 16, + }, + "source": "Pure", + "start": Position { + "column": 12, + "line": 1, + "offset": 12, + }, + }, + "name": "Pure", + }, + ], + "kind": "attrgroup", + "loc": Location { + "end": Position { + "column": 17, + "line": 1, + "offset": 17, + }, + "source": "#[Pure]", + "start": Position { + "column": 10, + "line": 1, + "offset": 10, + }, + }, + }, + ], + "hooks": [], + "kind": "property", + "loc": Location { + "end": Position { + "column": 36, + "line": 1, + "offset": 36, + }, + "source": "$foo = 1", + "start": Position { + "column": 28, + "line": 1, + "offset": 28, + }, + }, + "name": Identifier { + "kind": "identifier", + "loc": Location { + "end": Position { + "column": 32, + "line": 1, + "offset": 32, + }, + "source": "$foo", + "start": Position { + "column": 28, + "line": 1, + "offset": 28, + }, + }, + "name": "foo", + }, + "nullable": false, + "readonly": false, + "type": null, + "value": Number { + "kind": "number", + "loc": Location { + "end": Position { + "column": 36, + "line": 1, + "offset": 36, + }, + "source": "1", + "start": Position { + "column": 35, + "line": 1, + "offset": 35, + }, + }, + "value": "1", + }, + }, + ], + "visibility": "protected", + }, + PropertyStatement { + "isAbstract": false, + "isFinal": false, + "isStatic": false, + "kind": "propertystatement", + "loc": Location { + "end": Position { + "column": 53, + "line": 1, + "offset": 53, + }, + "source": "public $bar = 2", + "start": Position { + "column": 38, + "line": 1, + "offset": 38, + }, + }, + "properties": [ + Property { + "attrGroups": [], + "hooks": [], + "kind": "property", + "loc": Location { + "end": Position { + "column": 53, + "line": 1, + "offset": 53, + }, + "source": "$bar = 2", + "start": Position { + "column": 45, + "line": 1, + "offset": 45, + }, + }, + "name": Identifier { + "kind": "identifier", + "loc": Location { + "end": Position { + "column": 49, + "line": 1, + "offset": 49, + }, + "source": "$bar", + "start": Position { + "column": 45, + "line": 1, + "offset": 45, + }, + }, + "name": "bar", + }, + "nullable": false, + "readonly": false, + "type": null, + "value": Number { + "kind": "number", + "loc": Location { + "end": Position { + "column": 53, + "line": 1, + "offset": 53, + }, + "source": "2", + "start": Position { + "column": 52, + "line": 1, + "offset": 52, + }, + }, + "value": "2", + }, + }, + ], + "visibility": "public", + }, + ], + "extends": null, + "implements": null, + "isAbstract": false, + "isAnonymous": false, + "isFinal": false, + "isReadonly": false, + "kind": "class", + "loc": Location { + "end": Position { + "column": 56, + "line": 1, + "offset": 56, + }, + "source": "class C { #[Pure] protected $foo = 1; public $bar = 2; }", + "start": Position { + "column": 0, + "line": 1, + "offset": 0, + }, + }, + "name": Identifier { + "kind": "identifier", + "loc": Location { + "end": Position { + "column": 7, + "line": 1, + "offset": 7, + }, + "source": "C", + "start": Position { + "column": 6, + "line": 1, + "offset": 6, + }, + }, + "name": "C", + }, + }, + ], + "errors": [], + "kind": "program", + "loc": Location { + "end": Position { + "column": 56, + "line": 1, + "offset": 56, + }, + "source": "class C { #[Pure] protected $foo = 1; public $bar = 2; }", + "start": Position { + "column": 0, + "line": 1, + "offset": 0, + }, + }, +} +`; + exports[`Test locations test retif 1`] = ` Program { "children": [ diff --git a/test/snapshot/__snapshots__/namespace.test.js.snap b/test/snapshot/__snapshots__/namespace.test.js.snap index cb9cedb36..8049d3fb8 100644 --- a/test/snapshot/__snapshots__/namespace.test.js.snap +++ b/test/snapshot/__snapshots__/namespace.test.js.snap @@ -271,9 +271,9 @@ Program { }, "source": null, "start": Position { - "column": 15, + "column": 8, "line": 10, - "offset": 184, + "offset": 177, }, }, "properties": [ @@ -811,9 +811,9 @@ Program { }, "source": null, "start": Position { - "column": 15, + "column": 8, "line": 10, - "offset": 184, + "offset": 177, }, }, "properties": [ diff --git a/test/snapshot/location.test.js b/test/snapshot/location.test.js index 9751f6397..3e8e1b2cd 100644 --- a/test/snapshot/location.test.js +++ b/test/snapshot/location.test.js @@ -230,6 +230,14 @@ string";`, ["dnf type parameter", "function foo((A&B)|null $bar) {}"], ["attribute", "#[Deprecated(reason: 'since 5.2')] function foo() {}"], ["attrgroup", "#[Pure] #[Deprecated] function foo() {}"], + [ + "classconstant attribute loc includes attrgroup", + "class C { #[Deprecated] public const FOO = 1; public $bar = 2; }", + ], + [ + "propertystatement attribute loc includes attrgroup", + "class C { #[Pure] protected $foo = 1; public $bar = 2; }", + ], ])("test %s", (_, code) => { expect( parser.parseEval(code, {