From 3d03149e68fd069c695029d1e53979173601e1e1 Mon Sep 17 00:00:00 2001 From: Orest Divintari Date: Thu, 30 Oct 2025 21:05:59 +0100 Subject: [PATCH 01/12] Print int custom raw value --- lib/PhpParser/PrettyPrinter/Standard.php | 4 +++- test/PhpParser/PrettyPrinterTest.php | 6 ++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/PhpParser/PrettyPrinter/Standard.php b/lib/PhpParser/PrettyPrinter/Standard.php index 4cf374f9e8..fab95af730 100644 --- a/lib/PhpParser/PrettyPrinter/Standard.php +++ b/lib/PhpParser/PrettyPrinter/Standard.php @@ -205,7 +205,9 @@ protected function pScalar_Int(Scalar\Int_ $node): string { $kind = $node->getAttribute('kind', Scalar\Int_::KIND_DEC); if (Scalar\Int_::KIND_DEC === $kind) { - return (string) $node->value; + return $node->getAttribute('shouldPrintRawValue') + ? (string) $node->getAttribute('rawValue') + : (string) $node->value; } if ($node->value < 0) { diff --git a/test/PhpParser/PrettyPrinterTest.php b/test/PhpParser/PrettyPrinterTest.php index 8e0e472179..424824f1e4 100644 --- a/test/PhpParser/PrettyPrinterTest.php +++ b/test/PhpParser/PrettyPrinterTest.php @@ -305,4 +305,10 @@ public function testInvalidIndent(): void { $this->expectExceptionMessage('Option "indent" must either be all spaces or a single tab'); new PrettyPrinter\Standard(['indent' => "\t "]); } + + public function testPrintCustomRawValue(): void { + $prettyPrinter = new PrettyPrinter\Standard(); + $result = $prettyPrinter->prettyPrintExpr(new Int_(1000, ['rawValue' => '10_00', 'shouldPrintRawValue' => true])); + $this->assertSame('10_00', $result); + } } From a668b4595c519e2599bbdeb7ed8e3b997314a1d0 Mon Sep 17 00:00:00 2001 From: Orest Divintari Date: Fri, 31 Oct 2025 15:08:01 +0100 Subject: [PATCH 02/12] Print raw value when kind is raw --- lib/PhpParser/Node/Scalar/Int_.php | 2 ++ lib/PhpParser/PrettyPrinter/Standard.php | 9 ++++++--- test/PhpParser/PrettyPrinterTest.php | 2 +- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/lib/PhpParser/Node/Scalar/Int_.php b/lib/PhpParser/Node/Scalar/Int_.php index bcc257a6a1..6b18b9280a 100644 --- a/lib/PhpParser/Node/Scalar/Int_.php +++ b/lib/PhpParser/Node/Scalar/Int_.php @@ -12,6 +12,8 @@ class Int_ extends Scalar { public const KIND_DEC = 10; public const KIND_HEX = 16; + public const KIND_RAW = 22; + /** @var int Number value */ public int $value; diff --git a/lib/PhpParser/PrettyPrinter/Standard.php b/lib/PhpParser/PrettyPrinter/Standard.php index fab95af730..2a6b9ebae4 100644 --- a/lib/PhpParser/PrettyPrinter/Standard.php +++ b/lib/PhpParser/PrettyPrinter/Standard.php @@ -204,10 +204,13 @@ protected function pScalar_Int(Scalar\Int_ $node): string { } $kind = $node->getAttribute('kind', Scalar\Int_::KIND_DEC); + + if (Scalar\Int_::KIND_RAW === $kind) { + return (string) $node->getAttribute('rawValue'); + } + if (Scalar\Int_::KIND_DEC === $kind) { - return $node->getAttribute('shouldPrintRawValue') - ? (string) $node->getAttribute('rawValue') - : (string) $node->value; + return (string) $node->value; } if ($node->value < 0) { diff --git a/test/PhpParser/PrettyPrinterTest.php b/test/PhpParser/PrettyPrinterTest.php index 424824f1e4..6e58bae8eb 100644 --- a/test/PhpParser/PrettyPrinterTest.php +++ b/test/PhpParser/PrettyPrinterTest.php @@ -308,7 +308,7 @@ public function testInvalidIndent(): void { public function testPrintCustomRawValue(): void { $prettyPrinter = new PrettyPrinter\Standard(); - $result = $prettyPrinter->prettyPrintExpr(new Int_(1000, ['rawValue' => '10_00', 'shouldPrintRawValue' => true])); + $result = $prettyPrinter->prettyPrintExpr(new Int_(1000, ['rawValue' => '10_00', 'kind' => Int_::KIND_RAW])); $this->assertSame('10_00', $result); } } From 4c681cc8a5774b40453ce08cb1348065f9bda9fd Mon Sep 17 00:00:00 2001 From: Orest Divintari Date: Fri, 31 Oct 2025 15:09:18 +0100 Subject: [PATCH 03/12] rename const to raw value --- lib/PhpParser/Node/Scalar/Int_.php | 2 +- lib/PhpParser/PrettyPrinter/Standard.php | 2 +- test/PhpParser/PrettyPrinterTest.php | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/PhpParser/Node/Scalar/Int_.php b/lib/PhpParser/Node/Scalar/Int_.php index 6b18b9280a..b6f049b43d 100644 --- a/lib/PhpParser/Node/Scalar/Int_.php +++ b/lib/PhpParser/Node/Scalar/Int_.php @@ -12,7 +12,7 @@ class Int_ extends Scalar { public const KIND_DEC = 10; public const KIND_HEX = 16; - public const KIND_RAW = 22; + public const KIND_RAW_VALUE = 22; /** @var int Number value */ public int $value; diff --git a/lib/PhpParser/PrettyPrinter/Standard.php b/lib/PhpParser/PrettyPrinter/Standard.php index 2a6b9ebae4..ff7fa88b55 100644 --- a/lib/PhpParser/PrettyPrinter/Standard.php +++ b/lib/PhpParser/PrettyPrinter/Standard.php @@ -205,7 +205,7 @@ protected function pScalar_Int(Scalar\Int_ $node): string { $kind = $node->getAttribute('kind', Scalar\Int_::KIND_DEC); - if (Scalar\Int_::KIND_RAW === $kind) { + if (Scalar\Int_::KIND_RAW_VALUE === $kind) { return (string) $node->getAttribute('rawValue'); } diff --git a/test/PhpParser/PrettyPrinterTest.php b/test/PhpParser/PrettyPrinterTest.php index 6e58bae8eb..c35aa9f60f 100644 --- a/test/PhpParser/PrettyPrinterTest.php +++ b/test/PhpParser/PrettyPrinterTest.php @@ -308,7 +308,7 @@ public function testInvalidIndent(): void { public function testPrintCustomRawValue(): void { $prettyPrinter = new PrettyPrinter\Standard(); - $result = $prettyPrinter->prettyPrintExpr(new Int_(1000, ['rawValue' => '10_00', 'kind' => Int_::KIND_RAW])); + $result = $prettyPrinter->prettyPrintExpr(new Int_(1000, ['rawValue' => '10_00', 'kind' => Int_::KIND_RAW_VALUE])); $this->assertSame('10_00', $result); } } From 1b95278baf51732008c99261eeaa4505e82216cf Mon Sep 17 00:00:00 2001 From: Orest Divintari Date: Fri, 31 Oct 2025 15:20:22 +0100 Subject: [PATCH 04/12] move raw value check last --- lib/PhpParser/PrettyPrinter/Standard.php | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/PhpParser/PrettyPrinter/Standard.php b/lib/PhpParser/PrettyPrinter/Standard.php index ff7fa88b55..d620d2d654 100644 --- a/lib/PhpParser/PrettyPrinter/Standard.php +++ b/lib/PhpParser/PrettyPrinter/Standard.php @@ -205,10 +205,6 @@ protected function pScalar_Int(Scalar\Int_ $node): string { $kind = $node->getAttribute('kind', Scalar\Int_::KIND_DEC); - if (Scalar\Int_::KIND_RAW_VALUE === $kind) { - return (string) $node->getAttribute('rawValue'); - } - if (Scalar\Int_::KIND_DEC === $kind) { return (string) $node->value; } @@ -228,6 +224,11 @@ protected function pScalar_Int(Scalar\Int_ $node): string { case Scalar\Int_::KIND_HEX: return $sign . '0x' . base_convert($str, 10, 16); } + + if (Scalar\Int_::KIND_RAW_VALUE === $kind) { + return (string) $node->getAttribute('rawValue'); + } + throw new \Exception('Invalid number kind'); } From 4bebbd9a9d7b1dae06af7454d925ddb28f94fb50 Mon Sep 17 00:00:00 2001 From: Orest Divintari Date: Fri, 31 Oct 2025 16:25:43 +0100 Subject: [PATCH 05/12] Support raw value from string --- lib/PhpParser/Node/Scalar/Int_.php | 4 ++++ test/PhpParser/PrettyPrinterTest.php | 7 +++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/lib/PhpParser/Node/Scalar/Int_.php b/lib/PhpParser/Node/Scalar/Int_.php index b6f049b43d..f7d88c7fbc 100644 --- a/lib/PhpParser/Node/Scalar/Int_.php +++ b/lib/PhpParser/Node/Scalar/Int_.php @@ -42,6 +42,10 @@ public function getSubNodeNames(): array { * @return Int_ The constructed LNumber, including kind attribute */ public static function fromString(string $str, array $attributes = [], bool $allowInvalidOctal = false): Int_ { + if(isset($attributes['kind']) && $attributes['kind'] === self::KIND_RAW_VALUE){ + return new Int_((int) $str, $attributes); + } + $attributes['rawValue'] = $str; $str = str_replace('_', '', $str); diff --git a/test/PhpParser/PrettyPrinterTest.php b/test/PhpParser/PrettyPrinterTest.php index c35aa9f60f..532dfdb4dd 100644 --- a/test/PhpParser/PrettyPrinterTest.php +++ b/test/PhpParser/PrettyPrinterTest.php @@ -308,7 +308,10 @@ public function testInvalidIndent(): void { public function testPrintCustomRawValue(): void { $prettyPrinter = new PrettyPrinter\Standard(); - $result = $prettyPrinter->prettyPrintExpr(new Int_(1000, ['rawValue' => '10_00', 'kind' => Int_::KIND_RAW_VALUE])); - $this->assertSame('10_00', $result); + $attributes = ['rawValue' => '10_00', 'kind' => Int_::KIND_RAW_VALUE]; + $expected = '10_00'; + + $this->assertSame($expected, $prettyPrinter->prettyPrintExpr(new Int_(1000, $attributes))); + $this->assertSame($expected, $prettyPrinter->prettyPrintExpr(Int_::fromString('1000', $attributes))); } } From 8de1a1724b6f016179251043e7408547bf61a5a6 Mon Sep 17 00:00:00 2001 From: Orest Divintari Date: Fri, 31 Oct 2025 16:57:59 +0100 Subject: [PATCH 06/12] reuse expected variable for attribute --- test/PhpParser/PrettyPrinterTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/PhpParser/PrettyPrinterTest.php b/test/PhpParser/PrettyPrinterTest.php index 532dfdb4dd..1d4135c2f0 100644 --- a/test/PhpParser/PrettyPrinterTest.php +++ b/test/PhpParser/PrettyPrinterTest.php @@ -308,8 +308,8 @@ public function testInvalidIndent(): void { public function testPrintCustomRawValue(): void { $prettyPrinter = new PrettyPrinter\Standard(); - $attributes = ['rawValue' => '10_00', 'kind' => Int_::KIND_RAW_VALUE]; $expected = '10_00'; + $attributes = ['rawValue' => $expected, 'kind' => Int_::KIND_RAW_VALUE]; $this->assertSame($expected, $prettyPrinter->prettyPrintExpr(new Int_(1000, $attributes))); $this->assertSame($expected, $prettyPrinter->prettyPrintExpr(Int_::fromString('1000', $attributes))); From f37e459c6cfdc56b42cffe6a16dedbd3b610257a Mon Sep 17 00:00:00 2001 From: Orest Divintari Date: Fri, 31 Oct 2025 16:59:49 +0100 Subject: [PATCH 07/12] rename to var to rawValue --- test/PhpParser/PrettyPrinterTest.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/PhpParser/PrettyPrinterTest.php b/test/PhpParser/PrettyPrinterTest.php index 1d4135c2f0..2e062735b0 100644 --- a/test/PhpParser/PrettyPrinterTest.php +++ b/test/PhpParser/PrettyPrinterTest.php @@ -308,10 +308,10 @@ public function testInvalidIndent(): void { public function testPrintCustomRawValue(): void { $prettyPrinter = new PrettyPrinter\Standard(); - $expected = '10_00'; - $attributes = ['rawValue' => $expected, 'kind' => Int_::KIND_RAW_VALUE]; + $rawValue = '10_00'; + $attributes = ['rawValue' => $rawValue, 'kind' => Int_::KIND_RAW_VALUE]; - $this->assertSame($expected, $prettyPrinter->prettyPrintExpr(new Int_(1000, $attributes))); - $this->assertSame($expected, $prettyPrinter->prettyPrintExpr(Int_::fromString('1000', $attributes))); + $this->assertSame($rawValue, $prettyPrinter->prettyPrintExpr(new Int_(1000, $attributes))); + $this->assertSame($rawValue, $prettyPrinter->prettyPrintExpr(Int_::fromString('1000', $attributes))); } } From 5c2853d706d4177a4787ec7b0ff4bf2d5338e3f8 Mon Sep 17 00:00:00 2001 From: Orest Divintari Date: Sat, 29 Nov 2025 19:06:00 +0100 Subject: [PATCH 08/12] print raw value using shouldPrintRawValue --- lib/PhpParser/Node/Scalar/Int_.php | 6 ------ lib/PhpParser/PrettyPrinter/Standard.php | 9 +++------ test/PhpParser/PrettyPrinterTest.php | 6 ++---- 3 files changed, 5 insertions(+), 16 deletions(-) diff --git a/lib/PhpParser/Node/Scalar/Int_.php b/lib/PhpParser/Node/Scalar/Int_.php index f7d88c7fbc..bcc257a6a1 100644 --- a/lib/PhpParser/Node/Scalar/Int_.php +++ b/lib/PhpParser/Node/Scalar/Int_.php @@ -12,8 +12,6 @@ class Int_ extends Scalar { public const KIND_DEC = 10; public const KIND_HEX = 16; - public const KIND_RAW_VALUE = 22; - /** @var int Number value */ public int $value; @@ -42,10 +40,6 @@ public function getSubNodeNames(): array { * @return Int_ The constructed LNumber, including kind attribute */ public static function fromString(string $str, array $attributes = [], bool $allowInvalidOctal = false): Int_ { - if(isset($attributes['kind']) && $attributes['kind'] === self::KIND_RAW_VALUE){ - return new Int_((int) $str, $attributes); - } - $attributes['rawValue'] = $str; $str = str_replace('_', '', $str); diff --git a/lib/PhpParser/PrettyPrinter/Standard.php b/lib/PhpParser/PrettyPrinter/Standard.php index d620d2d654..1701f0e56a 100644 --- a/lib/PhpParser/PrettyPrinter/Standard.php +++ b/lib/PhpParser/PrettyPrinter/Standard.php @@ -206,7 +206,9 @@ protected function pScalar_Int(Scalar\Int_ $node): string { $kind = $node->getAttribute('kind', Scalar\Int_::KIND_DEC); if (Scalar\Int_::KIND_DEC === $kind) { - return (string) $node->value; + return $node->getAttribute('shouldPrintRawValue') + ? (string) $node->getAttribute('rawValue') + : (string) $node->value; } if ($node->value < 0) { @@ -224,11 +226,6 @@ protected function pScalar_Int(Scalar\Int_ $node): string { case Scalar\Int_::KIND_HEX: return $sign . '0x' . base_convert($str, 10, 16); } - - if (Scalar\Int_::KIND_RAW_VALUE === $kind) { - return (string) $node->getAttribute('rawValue'); - } - throw new \Exception('Invalid number kind'); } diff --git a/test/PhpParser/PrettyPrinterTest.php b/test/PhpParser/PrettyPrinterTest.php index 2e062735b0..1e6f4ec60a 100644 --- a/test/PhpParser/PrettyPrinterTest.php +++ b/test/PhpParser/PrettyPrinterTest.php @@ -308,10 +308,8 @@ public function testInvalidIndent(): void { public function testPrintCustomRawValue(): void { $prettyPrinter = new PrettyPrinter\Standard(); - $rawValue = '10_00'; - $attributes = ['rawValue' => $rawValue, 'kind' => Int_::KIND_RAW_VALUE]; + $node = new Int_(1000, ['rawValue' => '10_00', 'shouldPrintRawValue' => true]); - $this->assertSame($rawValue, $prettyPrinter->prettyPrintExpr(new Int_(1000, $attributes))); - $this->assertSame($rawValue, $prettyPrinter->prettyPrintExpr(Int_::fromString('1000', $attributes))); + $this->assertSame('10_00', $prettyPrinter->prettyPrintExpr($node)); } } From e3ffa2faafc1a3930b13d497d96944737e188d4e Mon Sep 17 00:00:00 2001 From: Orest Divintari Date: Sat, 29 Nov 2025 19:32:21 +0100 Subject: [PATCH 09/12] Support raw value for all integer formats --- lib/PhpParser/PrettyPrinter/Standard.php | 8 +++++--- test/PhpParser/PrettyPrinterTest.php | 22 ++++++++++++++++++++++ 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/lib/PhpParser/PrettyPrinter/Standard.php b/lib/PhpParser/PrettyPrinter/Standard.php index 1701f0e56a..e6a599e10c 100644 --- a/lib/PhpParser/PrettyPrinter/Standard.php +++ b/lib/PhpParser/PrettyPrinter/Standard.php @@ -197,6 +197,10 @@ protected function pScalar_InterpolatedString(Scalar\InterpolatedString $node): } protected function pScalar_Int(Scalar\Int_ $node): string { + if ($node->getAttribute('shouldPrintRawValue') === true) { + return (string) $node->getAttribute('rawValue'); + } + if ($node->value === -\PHP_INT_MAX - 1) { // PHP_INT_MIN cannot be represented as a literal, // because the sign is not part of the literal @@ -206,9 +210,7 @@ protected function pScalar_Int(Scalar\Int_ $node): string { $kind = $node->getAttribute('kind', Scalar\Int_::KIND_DEC); if (Scalar\Int_::KIND_DEC === $kind) { - return $node->getAttribute('shouldPrintRawValue') - ? (string) $node->getAttribute('rawValue') - : (string) $node->value; + return (string) $node->value; } if ($node->value < 0) { diff --git a/test/PhpParser/PrettyPrinterTest.php b/test/PhpParser/PrettyPrinterTest.php index 1e6f4ec60a..bcabcd78a4 100644 --- a/test/PhpParser/PrettyPrinterTest.php +++ b/test/PhpParser/PrettyPrinterTest.php @@ -165,6 +165,28 @@ public static function provideTestUnnaturalLiterals() { ]; } + /** @dataProvider provideTestCustomRawValue */ + public function printCustomRawValue($node, $expected): void { + $prettyPrinter = new PrettyPrinter\Standard(); + $result = $prettyPrinter->prettyPrintExpr($node); + $this->assertSame($expected, $result); + } + + public static function provideTestCustomRawValue() { + return [ + // Decimal with separator + [new Int_(1000, ['rawValue' => '10_00', 'shouldPrintRawValue' => true]), '10_00'], + // Hexadecimal with separator + [new Int_(0xDEADBEEF, ['kind' => Int_::KIND_HEX, 'rawValue' => '0xDEAD_BEEF', 'shouldPrintRawValue' => true]), '0xDEAD_BEEF'], + // Binary with separator + [new Int_(0b11110000, ['kind' => Int_::KIND_BIN, 'rawValue' => '0b1111_0000', 'shouldPrintRawValue' => true]), '0b1111_0000'], + // Octal with separator + [new Int_(0755, ['kind' => Int_::KIND_OCT, 'rawValue' => '0755_000', 'shouldPrintRawValue' => true]), '0755_000'], + // Without flag set, should use default formatting + [new Int_(1000, ['rawValue' => '10_00', 'shouldPrintRawValue' => false]), '1000'], + ]; + } + public function testPrettyPrintWithError(): void { $this->expectException(\LogicException::class); $this->expectExceptionMessage('Cannot pretty-print AST with Error nodes'); From 8004c8fa141cfe69ab129fa368cb5fc9205dccf5 Mon Sep 17 00:00:00 2001 From: Orest Divintari Date: Sun, 30 Nov 2025 17:50:31 +0100 Subject: [PATCH 10/12] Remove redundant test --- test/PhpParser/PrettyPrinterTest.php | 7 ------- 1 file changed, 7 deletions(-) diff --git a/test/PhpParser/PrettyPrinterTest.php b/test/PhpParser/PrettyPrinterTest.php index bcabcd78a4..aa956377ad 100644 --- a/test/PhpParser/PrettyPrinterTest.php +++ b/test/PhpParser/PrettyPrinterTest.php @@ -327,11 +327,4 @@ public function testInvalidIndent(): void { $this->expectExceptionMessage('Option "indent" must either be all spaces or a single tab'); new PrettyPrinter\Standard(['indent' => "\t "]); } - - public function testPrintCustomRawValue(): void { - $prettyPrinter = new PrettyPrinter\Standard(); - $node = new Int_(1000, ['rawValue' => '10_00', 'shouldPrintRawValue' => true]); - - $this->assertSame('10_00', $prettyPrinter->prettyPrintExpr($node)); - } } From 465940a72662795039bbf1df500de5c297dff763 Mon Sep 17 00:00:00 2001 From: Orest Divintari Date: Sun, 30 Nov 2025 17:53:45 +0100 Subject: [PATCH 11/12] Document rawValue printing --- doc/component/Pretty_printing.markdown | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/doc/component/Pretty_printing.markdown b/doc/component/Pretty_printing.markdown index db6ea1dc83..1a09143836 100644 --- a/doc/component/Pretty_printing.markdown +++ b/doc/component/Pretty_printing.markdown @@ -36,6 +36,10 @@ The pretty printer respects a number of attributes used by some nodes: * `kind` on `Scalar\String_` to use single quotes (default), double quotes, heredoc or nowdoc. In the latter two cases, the heredoc/nowdoc label from the `docLabel` attribute is used. * `kind` on `Scalar\Int_` to use decimal (default), binary, octal or hexadecimal representation. + * `shouldPrintRawValue` and `rawValue` on `Scalar\Int_` to preserve the original formatting of + integer literals (e.g., numeric separators like `1_000`). When `shouldPrintRawValue` is set to + `true`, the value from `rawValue` is used instead of the computed representation. This works for + all integer formats (decimal, binary, octal, hexadecimal). * `kind` on `Cast\Double` to use `(double)` (default), `(float)` or `(real)`. * `kind` on `Expr\List_` to use `[]` or `list()` (default depends on `phpVersion` option). * `kind` on `Expr\Array_` to use `[]` or `array()` (default depends on `shortArraySyntax` option). From ee2115b417595dd15dd4b983acee4faa478a619c Mon Sep 17 00:00:00 2001 From: Orest Divintari Date: Sun, 30 Nov 2025 21:15:44 +0100 Subject: [PATCH 12/12] Remove string casting --- lib/PhpParser/PrettyPrinter/Standard.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/PhpParser/PrettyPrinter/Standard.php b/lib/PhpParser/PrettyPrinter/Standard.php index e6a599e10c..d70843776e 100644 --- a/lib/PhpParser/PrettyPrinter/Standard.php +++ b/lib/PhpParser/PrettyPrinter/Standard.php @@ -198,7 +198,7 @@ protected function pScalar_InterpolatedString(Scalar\InterpolatedString $node): protected function pScalar_Int(Scalar\Int_ $node): string { if ($node->getAttribute('shouldPrintRawValue') === true) { - return (string) $node->getAttribute('rawValue'); + return $node->getAttribute('rawValue'); } if ($node->value === -\PHP_INT_MAX - 1) {