Compare commits

...

15 Commits

Author SHA1 Message Date
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 #662.

(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 (#862)
(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 #875.

(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
24 changed files with 1466 additions and 1049 deletions

View File

@@ -1,7 +1,35 @@
Version 4.14.1-dev
------------------
Version 4.15.2 (2022-11-12)
---------------------------
Nothing yet.
### 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)
---------------------------

View File

@@ -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

View File

@@ -571,7 +571,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 +585,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:

View File

@@ -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);

View File

@@ -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.
*

View File

@@ -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));
}
}

View File

@@ -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

View File

@@ -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,
];
/**

View File

@@ -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 = []) {

File diff suppressed because it is too large Load Diff

View File

@@ -664,6 +664,7 @@ abstract class ParserAbstract implements Parser
'false' => true,
'mixed' => true,
'never' => true,
'true' => true,
];
if (!$name->isUnqualified()) {

View File

@@ -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) {

View File

@@ -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

View File

@@ -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));

View File

@@ -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'],

View File

@@ -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(

View File

@@ -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 {};

View File

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

View File

@@ -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();
}

View File

@@ -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
)
)
)

View File

@@ -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(
)
)
)
)
)

View File

@@ -0,0 +1,158 @@
DNF types
-----
<?php
class Test {
public (A&B)|(X&Y) $prop;
}
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_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(
)
)
)

View File

@@ -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(
)
)
)

View File

@@ -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)
{
}