1
0
mirror of https://github.com/nikic/PHP-Parser.git synced 2025-07-16 20:06:32 +02:00

Compare commits

...

22 Commits

Author SHA1 Message Date
Nikita Popov
6bb5176bc4 Release PHP-Parser 4.15.4 2023-03-05 20:49:14 +01:00
Nikita Popov
cad49f8ed3 Fix formatting preservation for alternative elseif/else syntax
Test taken from PR .

(cherry picked from commit 9b46dffb12)
2023-03-05 20:44:50 +01:00
Nikita Popov
570e980a20 Release PHP-Parser 4.15.3 2023-01-16 23:05:37 +01:00
Nikita Popov
a50b4310f7 Test PHP 8.2 in CI 2023-01-16 22:27:18 +01:00
Nikita Popov
8863f92b58 Support readonly before DNF type
This makes us match the PHP 8.2 handling of readonly. Handling of
"readonly" functions is moved to the parser to allow distinguishing
them from readonly properties with DNF types. We have to uglify the
grammar to avoid some shift/reduce conflicts. Thank you WordPress.

(cherry-picked from 0dd85ebd34)
2023-01-16 22:19:17 +01:00
Nikita Popov
3182d12b55 Fix attrGroups/attributes confusion in EnumCase builder
Found by staabm in .

(cherry picked from commit 21a3e8cac5)
2022-12-14 21:51:15 +01:00
AlessandroMinoccheri
1df465cd90 added type int to argument of addAlias function 2022-11-13 16:33:17 +01:00
Nikita Popov
f59bbe44bf Release PHP-Parser 4.15.2 2022-11-12 16:38:23 +01:00
Nikita Popov
2e11deec46 Bail out on PHP tags in removed code
If dropping a node would drop PHP tags, bail out of formatting
preservation. This will lose formatting, but at least produce
legal code.

Closes GH-884.

(cherry picked from commit b0edd4c411)
2022-11-12 16:33:34 +01:00
Nikita Popov
a4fe65bf60 Add more tests for formatting preservation with InlineHTML
It's all broken...

(cherry picked from commit bad10e1618)
2022-11-12 16:33:21 +01:00
Nikita Popov
e072fd2c30 Adjust tests to work on 32-bit
Fixes .

(cherry picked from commit 950bf8f1d1)
2022-11-12 16:24:01 +01:00
Nikita Popov
7027899d7f Fix parsing of large hex floats containing "e"
These ended up taking the code path for normal floats and being
cast to zero.

(cherry picked from commit 4ce9781260)
2022-11-12 16:24:01 +01:00
Jaroslav Hanslík
2f1fd784fe Fixed type in UnionType 2022-09-10 22:41:13 +02:00
Nikita Popov
0ef6c55a3f Release PHP-Parser 4.15.1 2022-09-04 09:30:47 +02:00
Nikita Popov
8216e878be Fix empty list insertion of multiple attributes
(cherry picked from commit 44c6a97705)
2022-09-04 09:29:05 +02:00
Nikita Popov
617d0220b9 Release PHP-Parser 4.15.0 2022-09-03 21:54:32 +02:00
George Peter Banyard
a951e9e24d Add support for DNF types ()
(cherry picked from commit 9b2a01aa0c)
2022-09-03 21:50:20 +02:00
Nikita Popov
b30e7e73d5 Support empty list insertion for attributes
(cherry picked from commit a2608f0b74)
2022-09-03 21:48:21 +02:00
Nikita Popov
ff24d1d61a Fix length bounds check in Name::slice()
The length check did not take into account that there may be a
non-zero offset at this point.

Fixes .

(cherry picked from commit 53b907d405)
2022-09-03 21:47:28 +02:00
Anton
e55f8c6b30 Add __serialize/__unserialize to ClassMethod::$magicNames
(cherry picked from commit a3f2bb634d)
2022-09-03 21:46:15 +02:00
Nikita Popov
3ee592b6aa Handle true/false/null types in builder APIs
(cherry picked from commit 71ed641cd4)
2022-09-03 21:45:46 +02:00
Nikita Popov
3fe2422e34 Add support for true type
The null/false types were alread accepted previously, even though
they are only legal as standalone types since PHP 8.2.

Non-trivial backport from 1eba33d4124a248dd5c970fa7d6064d2e31b8635.
2022-09-03 21:36:40 +02:00
37 changed files with 3439 additions and 2800 deletions

@@ -40,9 +40,7 @@ jobs:
- "7.4"
- "8.0"
- "8.1"
include:
- php-version: "8.1"
flags: "--ignore-platform-req=php"
- "8.2"
steps:
- name: "Checkout"
uses: "actions/checkout@v2"

@@ -1,7 +1,50 @@
Version 4.14.1-dev
------------------
Version 4.15.4 (2023-03-05)
---------------------------
Nothing yet.
### Fixed
* Fixed formatting-preservation for alternative if syntax with trailing comments.
Version 4.15.3 (2023-01-16)
---------------------------
### Fixed
* Support readonly property with PHP 8.2 DNF type.
* Fixed PHP attribute group and PHP-Parser attribute mixup in EnumCase builder.
Version 4.15.2 (2022-11-12)
---------------------------
### Fixed
* Fixed parsing of large hex float literals that contain an "e" character.
* Fixed tests to pass on 32-bit.
* Fixed generation of invalid code when using formatting-preserving pretty printer with code that
uses inline HTML.
Version 4.15.1 (2022-09-04)
---------------------------
### Fixed
* Fixed formatting preservation when adding *multiple* attributes to a class/method/etc that
previously had none. This fixes a regression in the 4.15.0 release.
Version 4.15.0 (2022-09-03)
---------------------------
### Added
* PHP 8.2: Added support for `true` type.
* PHP 8.2: Added support for DNF types.
### Fixed
* Support `readonly` as a function name.
* Added `__serialize` and `__unserialize` to magic method list.
* Fixed bounds check in `Name::slice()`.
* Fixed formatting preservation when adding attributes to a class/method/etc that previously had none.
Version 4.14.0 (2022-05-31)
---------------------------

@@ -3,10 +3,10 @@ PHP Parser
[![Coverage Status](https://coveralls.io/repos/github/nikic/PHP-Parser/badge.svg?branch=master)](https://coveralls.io/github/nikic/PHP-Parser?branch=master)
This is a PHP 5.2 to PHP 8.1 parser written in PHP. Its purpose is to simplify static code analysis and
This is a PHP 5.2 to PHP 8.2 parser written in PHP. Its purpose is to simplify static code analysis and
manipulation.
[**Documentation for version 4.x**][doc_master] (stable; for running on PHP >= 7.0; for parsing PHP 5.2 to PHP 8.1).
[**Documentation for version 4.x**][doc_4_x] (stable; for running on PHP >= 7.0; for parsing PHP 5.2 to PHP 8.2).
[Documentation for version 3.x][doc_3_x] (unsupported; for running on PHP >= 5.5; for parsing PHP 5.2 to PHP 7.2).
@@ -222,4 +222,4 @@ Component documentation:
* Parent and sibling references
[doc_3_x]: https://github.com/nikic/PHP-Parser/tree/3.x/doc
[doc_master]: https://github.com/nikic/PHP-Parser/tree/master/doc
[doc_4_x]: https://github.com/nikic/PHP-Parser/tree/4.x/doc

@@ -264,8 +264,13 @@ optional_ellipsis:
| T_ELLIPSIS { $$ = true; }
;
identifier_maybe_readonly:
identifier { $$ = $1; }
| T_READONLY { $$ = Node\Identifier[$1]; }
;
function_declaration_statement:
T_FUNCTION optional_ref identifier '(' parameter_list ')' optional_return_type '{' inner_statement_list '}'
T_FUNCTION optional_ref identifier_maybe_readonly '(' parameter_list ')' optional_return_type '{' inner_statement_list '}'
{ $$ = Stmt\Function_[$3, ['byRef' => $2, 'params' => $5, 'returnType' => $7, 'stmts' => $9]]; }
;
@@ -721,8 +726,13 @@ lexical_var:
optional_ref plain_variable { $$ = Expr\ClosureUse[$2, $1]; }
;
name_readonly:
T_READONLY { $$ = Name[$1]; }
;
function_call:
name argument_list { $$ = Expr\FuncCall[$1, $2]; }
| name_readonly argument_list { $$ = Expr\FuncCall[$1, $2]; }
| class_name_or_var T_PAAMAYIM_NEKUDOTAYIM identifier_ex argument_list
{ $$ = Expr\StaticCall[$1, $3, $4]; }
| class_name_or_var T_PAAMAYIM_NEKUDOTAYIM '{' expr '}' argument_list

@@ -350,15 +350,23 @@ block_or_error:
| error { $$ = []; }
;
identifier_maybe_readonly:
identifier_not_reserved { $$ = $1; }
| T_READONLY { $$ = Node\Identifier[$1]; }
;
function_declaration_statement:
T_FUNCTION optional_ref identifier_not_reserved '(' parameter_list ')' optional_return_type block_or_error
T_FUNCTION optional_ref identifier_maybe_readonly '(' parameter_list ')' optional_return_type block_or_error
{ $$ = Stmt\Function_[$3, ['byRef' => $2, 'params' => $5, 'returnType' => $7, 'stmts' => $8, 'attrGroups' => []]]; }
| attributes T_FUNCTION optional_ref identifier_not_reserved '(' parameter_list ')' optional_return_type block_or_error
| attributes T_FUNCTION optional_ref identifier_maybe_readonly '(' parameter_list ')' optional_return_type block_or_error
{ $$ = Stmt\Function_[$4, ['byRef' => $3, 'params' => $6, 'returnType' => $8, 'stmts' => $9, 'attrGroups' => $1]]; }
;
class_declaration_statement:
optional_attributes class_entry_type identifier_not_reserved extends_from implements_list '{' class_statement_list '}'
class_entry_type identifier_not_reserved extends_from implements_list '{' class_statement_list '}'
{ $$ = Stmt\Class_[$2, ['type' => $1, 'extends' => $3, 'implements' => $4, 'stmts' => $6, 'attrGroups' => []]];
$this->checkClass($$, #2); }
| attributes class_entry_type identifier_not_reserved extends_from implements_list '{' class_statement_list '}'
{ $$ = Stmt\Class_[$3, ['type' => $2, 'extends' => $4, 'implements' => $5, 'stmts' => $7, 'attrGroups' => $1]];
$this->checkClass($$, #3); }
| optional_attributes T_INTERFACE identifier_not_reserved interface_extends_list '{' class_statement_list '}'
@@ -510,7 +518,8 @@ new_elseif_list:
;
new_elseif:
T_ELSEIF '(' expr ')' ':' inner_statement_list { $$ = Stmt\ElseIf_[$3, $6]; }
T_ELSEIF '(' expr ')' ':' inner_statement_list
{ $$ = Stmt\ElseIf_[$3, $6]; $this->fixupAlternativeElse($$); }
;
else_single:
@@ -520,7 +529,8 @@ else_single:
new_else_single:
/* empty */ { $$ = null; }
| T_ELSE ':' inner_statement_list { $$ = Stmt\Else_[$3]; }
| T_ELSE ':' inner_statement_list
{ $$ = Stmt\Else_[$3]; $this->fixupAlternativeElse($$); }
;
foreach_variable:
@@ -571,7 +581,7 @@ type_expr:
type { $$ = $1; }
| '?' type { $$ = Node\NullableType[$2]; }
| union_type { $$ = Node\UnionType[$1]; }
| intersection_type { $$ = Node\IntersectionType[$1]; }
| intersection_type { $$ = $1; }
;
type:
@@ -585,34 +595,52 @@ type_without_static:
| T_CALLABLE { $$ = Node\Identifier['callable']; }
;
union_type_element:
type { $$ = $1; }
| '(' intersection_type ')' { $$ = $2; }
;
union_type:
type '|' type { init($1, $3); }
| union_type '|' type { push($1, $3); }
union_type_element '|' union_type_element { init($1, $3); }
| union_type '|' union_type_element { push($1, $3); }
;
union_type_without_static_element:
type_without_static { $$ = $1; }
| '(' intersection_type_without_static ')' { $$ = $2; }
;
union_type_without_static:
type_without_static '|' type_without_static { init($1, $3); }
| union_type_without_static '|' type_without_static { push($1, $3); }
union_type_without_static_element '|' union_type_without_static_element { init($1, $3); }
| union_type_without_static '|' union_type_without_static_element { push($1, $3); }
;
intersection_type_list:
type T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG type { init($1, $3); }
| intersection_type_list T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG type
{ push($1, $3); }
;
intersection_type:
type T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG type { init($1, $3); }
| intersection_type T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG type
intersection_type_list { $$ = Node\IntersectionType[$1]; }
;
intersection_type_without_static_list:
type_without_static T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG type_without_static
{ init($1, $3); }
| intersection_type_without_static_list T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG type_without_static
{ push($1, $3); }
;
intersection_type_without_static:
type_without_static T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG type_without_static
{ init($1, $3); }
| intersection_type_without_static T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG type_without_static
{ push($1, $3); }
intersection_type_without_static_list { $$ = Node\IntersectionType[$1]; }
;
type_expr_without_static:
type_without_static { $$ = $1; }
| '?' type_without_static { $$ = Node\NullableType[$2]; }
| union_type_without_static { $$ = Node\UnionType[$1]; }
| intersection_type_without_static { $$ = Node\IntersectionType[$1]; }
| intersection_type_without_static { $$ = $1; }
;
optional_type_without_static:
@@ -944,8 +972,13 @@ lexical_var:
optional_ref plain_variable { $$ = Expr\ClosureUse[$2, $1]; }
;
name_readonly:
T_READONLY { $$ = Name[$1]; }
;
function_call:
name argument_list { $$ = Expr\FuncCall[$1, $2]; }
| name_readonly argument_list { $$ = Expr\FuncCall[$1, $2]; }
| callable_expr argument_list { $$ = Expr\FuncCall[$1, $2]; }
| class_name_or_var T_PAAMAYIM_NEKUDOTAYIM member_name argument_list
{ $$ = Expr\StaticCall[$1, $3, $4]; }

@@ -78,8 +78,8 @@ class EnumCase implements PhpParser\Builder
return new Stmt\EnumCase(
$this->name,
$this->value,
$this->attributes,
$this->attributeGroups
$this->attributeGroups,
$this->attributes
);
}
}

@@ -178,7 +178,20 @@ final class BuilderHelpers
}
$builtinTypes = [
'array', 'callable', 'string', 'int', 'float', 'bool', 'iterable', 'void', 'object', 'mixed', 'never',
'array',
'callable',
'bool',
'int',
'float',
'string',
'iterable',
'void',
'object',
'null',
'false',
'mixed',
'never',
'true',
];
$lowerType = strtolower($type);

@@ -206,6 +206,11 @@ class TokenStream
|| $this->haveTokenInRange($startPos, $endPos, '}');
}
public function haveTagInRange(int $startPos, int $endPos): bool {
return $this->haveTokenInRange($startPos, $endPos, \T_OPEN_TAG)
|| $this->haveTokenInRange($startPos, $endPos, \T_CLOSE_TAG);
}
/**
* Get indentation before token position.
*

@@ -14,6 +14,7 @@ use PhpParser\Lexer\TokenEmulator\FnTokenEmulator;
use PhpParser\Lexer\TokenEmulator\MatchTokenEmulator;
use PhpParser\Lexer\TokenEmulator\NullsafeTokenEmulator;
use PhpParser\Lexer\TokenEmulator\NumericLiteralSeparatorEmulator;
use PhpParser\Lexer\TokenEmulator\ReadonlyFunctionTokenEmulator;
use PhpParser\Lexer\TokenEmulator\ReadonlyTokenEmulator;
use PhpParser\Lexer\TokenEmulator\ReverseEmulator;
use PhpParser\Lexer\TokenEmulator\TokenEmulator;
@@ -24,6 +25,7 @@ class Emulative extends Lexer
const PHP_7_4 = '7.4dev';
const PHP_8_0 = '8.0dev';
const PHP_8_1 = '8.1dev';
const PHP_8_2 = '8.2dev';
/** @var mixed[] Patches used to reverse changes introduced in the code */
private $patches = [];
@@ -41,7 +43,7 @@ class Emulative extends Lexer
*/
public function __construct(array $options = [])
{
$this->targetPhpVersion = $options['phpVersion'] ?? Emulative::PHP_8_1;
$this->targetPhpVersion = $options['phpVersion'] ?? Emulative::PHP_8_2;
unset($options['phpVersion']);
parent::__construct($options);
@@ -57,6 +59,7 @@ class Emulative extends Lexer
new EnumTokenEmulator(),
new ReadonlyTokenEmulator(),
new ExplicitOctalEmulator(),
new ReadonlyFunctionTokenEmulator(),
];
// Collect emulators that are relevant for the PHP version we're running

@@ -0,0 +1,31 @@
<?php declare(strict_types=1);
namespace PhpParser\Lexer\TokenEmulator;
use PhpParser\Lexer\Emulative;
/*
* In PHP 8.1, "readonly(" was special cased in the lexer in order to support functions with
* name readonly. In PHP 8.2, this may conflict with readonly properties having a DNF type. For
* this reason, PHP 8.2 instead treats this as T_READONLY and then handles it specially in the
* parser. This emulator only exists to handle this special case, which is skipped by the
* PHP 8.1 ReadonlyTokenEmulator.
*/
class ReadonlyFunctionTokenEmulator extends KeywordEmulator {
public function getKeywordString(): string {
return 'readonly';
}
public function getKeywordToken(): int {
return \T_READONLY;
}
public function getPhpVersion(): string {
return Emulative::PHP_8_2;
}
public function reverseEmulate(string $code, array $tokens): array {
// Don't bother
return $tokens;
}
}

@@ -162,7 +162,7 @@ class Name extends NodeAbstract
$realLength = $numParts - $realOffset;
} else {
$realLength = $length < 0 ? $length + $numParts - $realOffset : $length;
if ($realLength < 0 || $realLength > $numParts) {
if ($realLength < 0 || $realLength > $numParts - $realOffset) {
throw new \OutOfBoundsException(sprintf('Length %d is out of bounds', $length));
}
}

@@ -47,13 +47,7 @@ class DNumber extends Scalar
public static function parse(string $str) : float {
$str = str_replace('_', '', $str);
// if string contains any of .eE just cast it to float
if (false !== strpbrk($str, '.eE')) {
return (float) $str;
}
// otherwise it's an integer notation that overflowed into a float
// if it starts with 0 it's one of the special integer notations
// Check whether this is one of the special integer notations.
if ('0' === $str[0]) {
// hex
if ('x' === $str[1] || 'X' === $str[1]) {
@@ -65,10 +59,12 @@ class DNumber extends Scalar
return bindec($str);
}
// oct
// substr($str, 0, strcspn($str, '89')) cuts the string at the first invalid digit (8 or 9)
// so that only the digits before that are used
return octdec(substr($str, 0, strcspn($str, '89')));
// oct, but only if the string does not contain any of '.eE'.
if (false === strpbrk($str, '.eE')) {
// substr($str, 0, strcspn($str, '89')) cuts the string at the first invalid digit
// (8 or 9) so that only the digits before that are used.
return octdec(substr($str, 0, strcspn($str, '89')));
}
}
// dec

@@ -23,21 +23,23 @@ class ClassMethod extends Node\Stmt implements FunctionLike
public $attrGroups;
private static $magicNames = [
'__construct' => true,
'__destruct' => true,
'__call' => true,
'__callstatic' => true,
'__get' => true,
'__set' => true,
'__isset' => true,
'__unset' => true,
'__sleep' => true,
'__wakeup' => true,
'__tostring' => true,
'__set_state' => true,
'__clone' => true,
'__invoke' => true,
'__debuginfo' => true,
'__construct' => true,
'__destruct' => true,
'__call' => true,
'__callstatic' => true,
'__get' => true,
'__set' => true,
'__isset' => true,
'__unset' => true,
'__sleep' => true,
'__wakeup' => true,
'__tostring' => true,
'__set_state' => true,
'__clone' => true,
'__invoke' => true,
'__debuginfo' => true,
'__serialize' => true,
'__unserialize' => true,
];
/**

@@ -4,13 +4,13 @@ namespace PhpParser\Node;
class UnionType extends ComplexType
{
/** @var (Identifier|Name)[] Types */
/** @var (Identifier|Name|IntersectionType)[] Types */
public $types;
/**
* Constructs a union type.
*
* @param (Identifier|Name)[] $types Types
* @param (Identifier|Name|IntersectionType)[] $types Types
* @param array $attributes Additional attributes
*/
public function __construct(array $types, array $attributes = []) {

@@ -161,7 +161,7 @@ class NameResolver extends NodeVisitorAbstract
return null;
}
private function addAlias(Stmt\UseUse $use, $type, Name $prefix = null) {
private function addAlias(Stmt\UseUse $use, int $type, Name $prefix = null) {
// Add prefix for group uses
$name = $prefix ? Name::concat($prefix, $use->name) : $use->name;
// Type is determined either by individual element or whole use declaration

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

@@ -16,9 +16,12 @@ use PhpParser\Node\Scalar\String_;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\ClassConst;
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\Node\Stmt\Else_;
use PhpParser\Node\Stmt\ElseIf_;
use PhpParser\Node\Stmt\Enum_;
use PhpParser\Node\Stmt\Interface_;
use PhpParser\Node\Stmt\Namespace_;
use PhpParser\Node\Stmt\Nop;
use PhpParser\Node\Stmt\Property;
use PhpParser\Node\Stmt\TryCatch;
use PhpParser\Node\Stmt\UseUse;
@@ -664,6 +667,7 @@ abstract class ParserAbstract implements Parser
'false' => true,
'mixed' => true,
'never' => true,
'true' => true,
];
if (!$name->isUnqualified()) {
@@ -875,6 +879,24 @@ abstract class ParserAbstract implements Parser
return $attributes;
}
/** @param ElseIf_|Else_ $node */
protected function fixupAlternativeElse($node) {
// Make sure a trailing nop statement carrying comments is part of the node.
$numStmts = \count($node->stmts);
if ($numStmts !== 0 && $node->stmts[$numStmts - 1] instanceof Nop) {
$nopAttrs = $node->stmts[$numStmts - 1]->getAttributes();
if (isset($nopAttrs['endLine'])) {
$node->setAttribute('endLine', $nopAttrs['endLine']);
}
if (isset($nopAttrs['endFilePos'])) {
$node->setAttribute('endFilePos', $nopAttrs['endFilePos']);
}
if (isset($nopAttrs['endTokenPos'])) {
$node->setAttribute('endTokenPos', $nopAttrs['endTokenPos']);
}
}
}
protected function checkClassModifier($a, $b, $modifierPos) {
try {
Class_::verifyClassModifier($a, $b);

@@ -46,7 +46,15 @@ class Standard extends PrettyPrinterAbstract
}
protected function pUnionType(Node\UnionType $node) {
return $this->pImplode($node->types, '|');
$types = [];
foreach ($node->types as $typeNode) {
if ($typeNode instanceof Node\IntersectionType) {
$types[] = '('. $this->p($typeNode) . ')';
continue;
}
$types[] = $this->p($typeNode);
}
return implode('|', $types);
}
protected function pIntersectionType(Node\IntersectionType $node) {

@@ -774,7 +774,8 @@ abstract class PrettyPrinterAbstract
}
if ($skipRemovedNode) {
if ($isStmtList && $this->origTokens->haveBracesInRange($pos, $itemStartPos)) {
if ($isStmtList && ($this->origTokens->haveBracesInRange($pos, $itemStartPos) ||
$this->origTokens->haveTagInRange($pos, $itemStartPos))) {
// We'd remove the brace of a code block.
// TODO: Preserve formatting.
$this->setIndentLevel($origIndentLevel);
@@ -877,7 +878,8 @@ abstract class PrettyPrinterAbstract
$pos, $itemStartPos, $indentAdjustment);
$skipRemovedNode = true;
} else {
if ($isStmtList && $this->origTokens->haveBracesInRange($pos, $itemStartPos)) {
if ($isStmtList && ($this->origTokens->haveBracesInRange($pos, $itemStartPos) ||
$this->origTokens->haveTagInRange($pos, $itemStartPos))) {
// We'd remove the brace of a code block.
// TODO: Preserve formatting.
return null;
@@ -923,11 +925,14 @@ abstract class PrettyPrinterAbstract
foreach ($delayedAdd as $delayedAddNode) {
if (!$first) {
$result .= $insertStr;
if ($insertNewline) {
$result .= $this->nl;
}
}
$result .= $this->p($delayedAddNode, true);
$first = false;
}
$result .= $extraRight;
$result .= $extraRight === "\n" ? $this->nl : $extraRight;
}
return $result;
@@ -1454,6 +1459,16 @@ abstract class PrettyPrinterAbstract
'Stmt_ClassMethod->params' => ['(', '', ''],
'Stmt_Interface->extends' => [null, ' extends ', ''],
'Stmt_Function->params' => ['(', '', ''],
'Stmt_Interface->attrGroups' => [null, '', "\n"],
'Stmt_Class->attrGroups' => [null, '', "\n"],
'Stmt_ClassConst->attrGroups' => [null, '', "\n"],
'Stmt_ClassMethod->attrGroups' => [null, '', "\n"],
'Stmt_Function->attrGroups' => [null, '', "\n"],
'Stmt_Property->attrGroups' => [null, '', "\n"],
'Stmt_Trait->attrGroups' => [null, '', "\n"],
'Expr_ArrowFunction->attrGroups' => [null, '', ' '],
'Expr_Closure->attrGroups' => [null, '', ' '],
'Expr_PrintableNewAnonClass->attrGroups' => [\T_NEW, ' ', ''],
/* These cannot be empty to start with:
* Expr_Isset->vars

@@ -27,6 +27,7 @@ class EnumCaseTest extends \PHPUnit\Framework\TestCase
new Stmt\EnumCase(
"TEST",
null,
[],
[
'comments' => [new Comment\Doc('/** Test */')]
]
@@ -50,7 +51,6 @@ class EnumCaseTest extends \PHPUnit\Framework\TestCase
new Stmt\EnumCase(
"ATTR_GROUP",
null,
[],
[$attributeGroup]
),
$node

@@ -125,8 +125,11 @@ class BuilderHelpersTest extends \PHPUnit\Framework\TestCase
$this->assertEquals(new Node\Identifier('iterable'), BuilderHelpers::normalizeType('iterable'));
$this->assertEquals(new Node\Identifier('void'), BuilderHelpers::normalizeType('void'));
$this->assertEquals(new Node\Identifier('object'), BuilderHelpers::normalizeType('object'));
$this->assertEquals(new Node\Identifier('null'), BuilderHelpers::normalizeType('null'));
$this->assertEquals(new Node\Identifier('false'), BuilderHelpers::normalizeType('false'));
$this->assertEquals(new Node\Identifier('mixed'), BuilderHelpers::normalizeType('mixed'));
$this->assertEquals(new Node\Identifier('never'), BuilderHelpers::normalizeType('never'));
$this->assertEquals(new Node\Identifier('true'), BuilderHelpers::normalizeType('true'));
$intIdentifier = new Node\Identifier('int');
$this->assertSame($intIdentifier, BuilderHelpers::normalizeType($intIdentifier));

@@ -241,8 +241,8 @@ class EmulativeTest extends LexerTest
['1_000', [
[Tokens::T_LNUMBER, '1_000'],
]],
['0xCAFE_F00D', [
[Tokens::T_LNUMBER, '0xCAFE_F00D'],
['0x7AFE_F00D', [
[Tokens::T_LNUMBER, '0x7AFE_F00D'],
]],
['0b0101_1111', [
[Tokens::T_LNUMBER, '0b0101_1111'],
@@ -360,12 +360,12 @@ class EmulativeTest extends LexerTest
]],
['function readonly(', [
[Tokens::T_FUNCTION, 'function'],
[Tokens::T_STRING, 'readonly'],
[Tokens::T_READONLY, 'readonly'],
[ord('('), '('],
]],
['function readonly (', [
[Tokens::T_FUNCTION, 'function'],
[Tokens::T_STRING, 'readonly'],
[Tokens::T_READONLY, 'readonly'],
[ord('('), '('],
]],
];

@@ -73,6 +73,12 @@ class NameTest extends \PHPUnit\Framework\TestCase
(new Name('foo\bar\baz'))->slice(0, -4);
}
public function testSliceLengthTooLargeWithOffset() {
$this->expectException(\OutOfBoundsException::class);
$this->expectExceptionMessage('Length 3 is out of bounds');
(new Name('foo\bar\baz'))->slice(1, 3);
}
public function testConcat() {
$this->assertEquals(new Name('foo\bar\baz'), Name::concat('foo', 'bar\baz'));
$this->assertEquals(

@@ -102,12 +102,16 @@ function() {};
fn()
=> 42;
-----
// TODO: Currently we lose formatting for this case.
$attrGroup = new Node\AttributeGroup([
new Node\Attribute(new Node\Name('A'), []),
]);
$attrGroup2 = new Node\AttributeGroup([
new Node\Attribute(new Node\Name('B'), []),
]);
$stmts[0]->attrGroups[] = $attrGroup;
$stmts[0]->attrGroups[] = $attrGroup2;
$stmts[0]->stmts[0]->attrGroups[] = $attrGroup;
$stmts[0]->stmts[0]->attrGroups[] = $attrGroup2;
$stmts[0]->stmts[1]->attrGroups[] = $attrGroup;
$stmts[0]->stmts[2]->attrGroups[] = $attrGroup;
$stmts[1]->attrGroups[] = $attrGroup;
@@ -119,39 +123,34 @@ $stmts[6]->expr->attrGroups[] = $attrGroup;
-----
<?php
#[A]
class X
{
#[B]
class X {
#[A]
public function m()
{
}
#[B]
public function m() {}
#[A]
public $prop;
public
$prop;
#[A]
const X = 42;
const
X = 42;
}
#[A]
trait X
{
}
trait X {}
#[A]
interface X
{
}
interface X {}
#[A]
function f()
{
}
function f() {}
new #[A] class
{
};
#[A] function () {
};
#[A] fn() => 42;
new #[A] class {};
#[A] function() {};
#[A] fn()
=> 42;
-----
<?php
@@ -176,4 +175,4 @@ class X {};
B,
C,
]
class X {};
class X {};

@@ -49,4 +49,4 @@ class Test {
public function test() {
// some code
}
}
}

@@ -107,4 +107,4 @@ Foo
new class
($a, $b)
extends Foo {};
extends Foo {};

@@ -42,13 +42,71 @@ function test() {
baz();
}
-----
// TODO Fix broken result
// TODO Preserve formatting
$stmts[0]->stmts[1] = $stmts[0]->stmts[2];
-----
<?php
function test()
{
foo();
baz();
baz();
}
-----
<?php
function test() {
foo();<?php
foo();
?>Bar<?php
baz();
}
-----
// TODO Preserve formatting
unset($stmts[0]->stmts[2]);
-----
<?php
function test()
{
foo();
?>Bar<?php
}
-----
<?php
function test() {
foo();
?>Bar<?php
baz();
}
}
-----
// TODO Preserve formatting
array_splice($stmts[0]->stmts, 0, 1, []);
-----
<?php
function test()
{
?>Bar<?php
baz();
}
-----
<?php
function test() {
foo();
?>Bar<?php
baz();
}
-----
// TODO Preserve formatting
array_splice($stmts[0]->stmts, 1, 1, []);
-----
<?php
function test()
{
foo();
baz();
}

@@ -17,6 +17,7 @@ Different float syntaxes
// (all are actually the same number, just in different representations)
18446744073709551615;
0xFFFFFFFFFFFFFFFF;
0xEEEEEEEEEEEEEEEE;
01777777777777777777777;
0177777777777777777777787;
0b1111111111111111111111111111111111111111111111111111111111111111;
@@ -92,7 +93,7 @@ array(
)
12: Stmt_Expression(
expr: Scalar_DNumber(
value: 1.844674407371E+19
value: 1.7216961135462E+19
)
)
13: Stmt_Expression(
@@ -105,4 +106,9 @@ array(
value: 1.844674407371E+19
)
)
)
15: Stmt_Expression(
expr: Scalar_DNumber(
value: 1.844674407371E+19
)
)
)

@@ -4,7 +4,7 @@ Different integer syntaxes
6.674_083e-11;
299_792_458;
0xCAFE_F00D;
0x7AFE_F00D;
0b0101_1111;
0137_041;
@@ -42,7 +42,7 @@ array(
)
2: Stmt_Expression(
expr: Scalar_LNumber(
value: 3405705229
value: 2063527949
)
)
3: Stmt_Expression(
@@ -196,4 +196,4 @@ array(
)
)
)
)
)

@@ -280,7 +280,7 @@ array(
// Type in the partial parse could conceivably be any of 0, 16 or 32
-----
!!php5
Syntax error, unexpected T_READONLY from 1:7 to 1:14
Syntax error, unexpected T_CLASS, expecting '(' from 1:16 to 1:20
array(
0: Stmt_Class(
attrGroups: array(
@@ -366,4 +366,4 @@ array(
)
)
)
)
)

@@ -28,7 +28,7 @@ readonly class A {
}
-----
!!php5
Syntax error, unexpected T_READONLY from 3:1 to 3:8
Syntax error, unexpected T_CLASS, expecting '(' from 3:10 to 3:14
array(
0: Stmt_Class(
attrGroups: array(
@@ -65,4 +65,4 @@ array(
stmts: array(
)
)
)
)

@@ -0,0 +1,195 @@
DNF types
-----
<?php
class Test {
public (A&B)|(X&Y) $prop;
public readonly (A&B)|C $prop2;
}
function test((A&B)|(X&Y) $a): (A&B)|(X&Y) {}
-----
!!php7
array(
0: Stmt_Class(
attrGroups: array(
)
flags: 0
name: Identifier(
name: Test
)
extends: null
implements: array(
)
stmts: array(
0: Stmt_Property(
attrGroups: array(
)
flags: MODIFIER_PUBLIC (1)
type: UnionType(
types: array(
0: IntersectionType(
types: array(
0: Name(
parts: array(
0: A
)
)
1: Name(
parts: array(
0: B
)
)
)
)
1: IntersectionType(
types: array(
0: Name(
parts: array(
0: X
)
)
1: Name(
parts: array(
0: Y
)
)
)
)
)
)
props: array(
0: Stmt_PropertyProperty(
name: VarLikeIdentifier(
name: prop
)
default: null
)
)
)
1: Stmt_Property(
attrGroups: array(
)
flags: MODIFIER_PUBLIC | MODIFIER_READONLY (65)
type: UnionType(
types: array(
0: IntersectionType(
types: array(
0: Name(
parts: array(
0: A
)
)
1: Name(
parts: array(
0: B
)
)
)
)
1: Name(
parts: array(
0: C
)
)
)
)
props: array(
0: Stmt_PropertyProperty(
name: VarLikeIdentifier(
name: prop2
)
default: null
)
)
)
)
)
1: Stmt_Function(
attrGroups: array(
)
byRef: false
name: Identifier(
name: test
)
params: array(
0: Param(
attrGroups: array(
)
flags: 0
type: UnionType(
types: array(
0: IntersectionType(
types: array(
0: Name(
parts: array(
0: A
)
)
1: Name(
parts: array(
0: B
)
)
)
)
1: IntersectionType(
types: array(
0: Name(
parts: array(
0: X
)
)
1: Name(
parts: array(
0: Y
)
)
)
)
)
)
byRef: false
variadic: false
var: Expr_Variable(
name: a
)
default: null
)
)
returnType: UnionType(
types: array(
0: IntersectionType(
types: array(
0: Name(
parts: array(
0: A
)
)
1: Name(
parts: array(
0: B
)
)
)
)
1: IntersectionType(
types: array(
0: Name(
parts: array(
0: X
)
)
1: Name(
parts: array(
0: Y
)
)
)
)
)
)
stmts: array(
)
)
)

@@ -0,0 +1,56 @@
standalone null, false and true types
-----
<?php
function test(): null {}
function test(): false {}
function test(): true {}
-----
!!php7
array(
0: Stmt_Function(
attrGroups: array(
)
byRef: false
name: Identifier(
name: test
)
params: array(
)
returnType: Identifier(
name: null
)
stmts: array(
)
)
1: Stmt_Function(
attrGroups: array(
)
byRef: false
name: Identifier(
name: test
)
params: array(
)
returnType: Identifier(
name: false
)
stmts: array(
)
)
2: Stmt_Function(
attrGroups: array(
)
byRef: false
name: Identifier(
name: test
)
params: array(
)
returnType: Identifier(
name: true
)
stmts: array(
)
)
)

@@ -0,0 +1,32 @@
readonly function
-----
<?php
function readonly() {}
readonly();
-----
array(
0: Stmt_Function(
attrGroups: array(
)
byRef: false
name: Identifier(
name: readonly
)
params: array(
)
returnType: null
stmts: array(
)
)
1: Stmt_Expression(
expr: Expr_FuncCall(
name: Name(
parts: array(
0: readonly
)
)
args: array(
)
)
)
)

@@ -64,4 +64,28 @@ function test()
function test()
{
// empty
}
}
-----
<?php
function noDuplicateComment()
{
if (true):
// TEST 1
elseif (true):
// TEST 2
else:
// TEST 3
endif;
}
-----
function noDuplicateComment()
{
if (true) {
// TEST 1
} elseif (true) {
// TEST 2
} else {
// TEST 3
}
}

@@ -0,0 +1,18 @@
Union types
-----
<?php
class Test {
public (A&B)|(X&Y) $prop;
}
function test((A&B)|(X&Y) $a): (A&B)|(X&Y) {}
-----
!!php7
class Test
{
public (A&B)|(X&Y) $prop;
}
function test((A&B)|(X&Y) $a) : (A&B)|(X&Y)
{
}