Compare commits

..

32 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 #797.

(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 #907.

(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 #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
Nikita Popov
2d589921f2 Fix incorrect doc type 2022-06-04 12:44:36 +02:00
Nikita Popov
cdb731fa8b Update integration test target to 8.1 2022-05-31 23:18:52 +02:00
Nikita Popov
e727475d08 Support readonly as function name
This special case was added after the PHP 8.1 release.
2022-05-31 23:18:52 +02:00
Nikita Popov
34bea19b6e Release PHP-Parser 4.14.0 2022-05-31 22:59:12 +02:00
Tomas Votruba
678ccbe072 [PHP 8.2] Add readonly class support (#834)
RFC: https://wiki.php.net/rfc/readonly_classes

PHP implementation: php/php-src#7305
2022-05-15 23:19:31 +02:00
Tomas Votruba
5d83adcc0e [String_] Add rawValue attribute (#831) 2022-05-15 23:04:59 +02:00
Tomas Votruba
3bf0082455 [DNumber] Add rawValue attribute to hold the original value (#833) 2022-05-15 18:12:28 +02:00
Tomas Votruba
d3eb10aca1 [LNumber] Add rawValue attribute to LNumber to allow numeric separator etc. (#832) 2022-05-15 18:09:05 +02:00
Marijn van Wezel
a6e34665fd Reflect support for PHP 8.1 in the README 2022-01-03 21:20:19 +01:00
Ondrej Mirtes
f4b835f7d8 Fix PHPDoc type of namespacedName properties 2021-12-06 21:33:02 +01:00
56 changed files with 3858 additions and 2889 deletions

View File

@@ -40,9 +40,7 @@ jobs:
- "7.4" - "7.4"
- "8.0" - "8.0"
- "8.1" - "8.1"
include: - "8.2"
- php-version: "8.1"
flags: "--ignore-platform-req=php"
steps: steps:
- name: "Checkout" - name: "Checkout"
uses: "actions/checkout@v2" uses: "actions/checkout@v2"
@@ -74,7 +72,7 @@ jobs:
run: "test_old/run-php-src.sh 7.3.21" run: "test_old/run-php-src.sh 7.3.21"
test_old_80_70: test_old_80_70:
runs-on: "ubuntu-latest" runs-on: "ubuntu-latest"
name: "PHP 8.0 Code on PHP 7.0 Integration Tests" name: "PHP 8.1 Code on PHP 7.0 Integration Tests"
steps: steps:
- name: "Checkout" - name: "Checkout"
uses: "actions/checkout@v2" uses: "actions/checkout@v2"
@@ -87,4 +85,4 @@ jobs:
- name: "Install PHP 8 dependencies" - name: "Install PHP 8 dependencies"
run: "composer update --no-progress --prefer-dist" run: "composer update --no-progress --prefer-dist"
- name: "Tests" - name: "Tests"
run: "test_old/run-php-src.sh 8.0.0" run: "test_old/run-php-src.sh 8.1.6"

View File

@@ -1,7 +1,59 @@
Version 4.13.3-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)
---------------------------
### Added
* Added support for readonly classes.
* Added `rawValue` attribute to `LNumber`, `DNumber` and `String_` nodes, which stores the unparsed
value of the literal (e.g. `"1_000"` rather than `1000`).
Version 4.13.2 (2021-11-30) Version 4.13.2 (2021-11-30)
--------------------------- ---------------------------

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) [![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.0 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. manipulation.
[**Documentation for version 4.x**][doc_master] (stable; for running on PHP >= 7.0; for parsing PHP 5.2 to PHP 8.0). [**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). [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 * Parent and sibling references
[doc_3_x]: https://github.com/nikic/PHP-Parser/tree/3.x/doc [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

@@ -264,8 +264,13 @@ optional_ellipsis:
| T_ELLIPSIS { $$ = true; } | T_ELLIPSIS { $$ = true; }
; ;
identifier_maybe_readonly:
identifier { $$ = $1; }
| T_READONLY { $$ = Node\Identifier[$1]; }
;
function_declaration_statement: 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]]; } { $$ = Stmt\Function_[$3, ['byRef' => $2, 'params' => $5, 'returnType' => $7, 'stmts' => $9]]; }
; ;
@@ -689,9 +694,7 @@ array_expr:
scalar_dereference: scalar_dereference:
array_expr '[' dim_offset ']' { $$ = Expr\ArrayDimFetch[$1, $3]; } array_expr '[' dim_offset ']' { $$ = Expr\ArrayDimFetch[$1, $3]; }
| T_CONSTANT_ENCAPSED_STRING '[' dim_offset ']' | T_CONSTANT_ENCAPSED_STRING '[' dim_offset ']' { $$ = Expr\ArrayDimFetch[Scalar\String_::fromString($1, attributes()), $3]; }
{ $attrs = attributes(); $attrs['kind'] = strKind($1);
$$ = Expr\ArrayDimFetch[new Scalar\String_(Scalar\String_::parse($1), $attrs), $3]; }
| constant '[' dim_offset ']' { $$ = Expr\ArrayDimFetch[$1, $3]; } | constant '[' dim_offset ']' { $$ = Expr\ArrayDimFetch[$1, $3]; }
| scalar_dereference '[' dim_offset ']' { $$ = Expr\ArrayDimFetch[$1, $3]; } | scalar_dereference '[' dim_offset ']' { $$ = Expr\ArrayDimFetch[$1, $3]; }
/* alternative array syntax missing intentionally */ /* alternative array syntax missing intentionally */
@@ -723,8 +726,13 @@ lexical_var:
optional_ref plain_variable { $$ = Expr\ClosureUse[$2, $1]; } optional_ref plain_variable { $$ = Expr\ClosureUse[$2, $1]; }
; ;
name_readonly:
T_READONLY { $$ = Name[$1]; }
;
function_call: function_call:
name argument_list { $$ = Expr\FuncCall[$1, $2]; } 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 | class_name_or_var T_PAAMAYIM_NEKUDOTAYIM identifier_ex argument_list
{ $$ = Expr\StaticCall[$1, $3, $4]; } { $$ = Expr\StaticCall[$1, $3, $4]; }
| class_name_or_var T_PAAMAYIM_NEKUDOTAYIM '{' expr '}' argument_list | class_name_or_var T_PAAMAYIM_NEKUDOTAYIM '{' expr '}' argument_list
@@ -793,10 +801,8 @@ ctor_arguments:
common_scalar: common_scalar:
T_LNUMBER { $$ = $this->parseLNumber($1, attributes(), true); } T_LNUMBER { $$ = $this->parseLNumber($1, attributes(), true); }
| T_DNUMBER { $$ = Scalar\DNumber[Scalar\DNumber::parse($1)]; } | T_DNUMBER { $$ = Scalar\DNumber::fromString($1, attributes()); }
| T_CONSTANT_ENCAPSED_STRING | T_CONSTANT_ENCAPSED_STRING { $$ = Scalar\String_::fromString($1, attributes(), false); }
{ $attrs = attributes(); $attrs['kind'] = strKind($1);
$$ = new Scalar\String_(Scalar\String_::parse($1, false), $attrs); }
| T_LINE { $$ = Scalar\MagicConst\Line[]; } | T_LINE { $$ = Scalar\MagicConst\Line[]; }
| T_FILE { $$ = Scalar\MagicConst\File[]; } | T_FILE { $$ = Scalar\MagicConst\File[]; }
| T_DIR { $$ = Scalar\MagicConst\Dir[]; } | T_DIR { $$ = Scalar\MagicConst\Dir[]; }

View File

@@ -350,15 +350,23 @@ block_or_error:
| error { $$ = []; } | error { $$ = []; }
; ;
identifier_maybe_readonly:
identifier_not_reserved { $$ = $1; }
| T_READONLY { $$ = Node\Identifier[$1]; }
;
function_declaration_statement: 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' => []]]; } { $$ = 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]]; } { $$ = Stmt\Function_[$4, ['byRef' => $3, 'params' => $6, 'returnType' => $8, 'stmts' => $9, 'attrGroups' => $1]]; }
; ;
class_declaration_statement: 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]]; { $$ = Stmt\Class_[$3, ['type' => $2, 'extends' => $4, 'implements' => $5, 'stmts' => $7, 'attrGroups' => $1]];
$this->checkClass($$, #3); } $this->checkClass($$, #3); }
| optional_attributes T_INTERFACE identifier_not_reserved interface_extends_list '{' class_statement_list '}' | optional_attributes T_INTERFACE identifier_not_reserved interface_extends_list '{' class_statement_list '}'
@@ -382,8 +390,18 @@ enum_case_expr:
class_entry_type: class_entry_type:
T_CLASS { $$ = 0; } T_CLASS { $$ = 0; }
| T_ABSTRACT T_CLASS { $$ = Stmt\Class_::MODIFIER_ABSTRACT; } | class_modifiers T_CLASS { $$ = $1; }
| T_FINAL T_CLASS { $$ = Stmt\Class_::MODIFIER_FINAL; } ;
class_modifiers:
class_modifier { $$ = $1; }
| class_modifiers class_modifier { $this->checkClassModifier($1, $2, #2); $$ = $1 | $2; }
;
class_modifier:
T_ABSTRACT { $$ = Stmt\Class_::MODIFIER_ABSTRACT; }
| T_FINAL { $$ = Stmt\Class_::MODIFIER_FINAL; }
| T_READONLY { $$ = Stmt\Class_::MODIFIER_READONLY; }
; ;
extends_from: extends_from:
@@ -500,7 +518,8 @@ new_elseif_list:
; ;
new_elseif: 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: else_single:
@@ -510,7 +529,8 @@ else_single:
new_else_single: new_else_single:
/* empty */ { $$ = null; } /* empty */ { $$ = null; }
| T_ELSE ':' inner_statement_list { $$ = Stmt\Else_[$3]; } | T_ELSE ':' inner_statement_list
{ $$ = Stmt\Else_[$3]; $this->fixupAlternativeElse($$); }
; ;
foreach_variable: foreach_variable:
@@ -561,7 +581,7 @@ type_expr:
type { $$ = $1; } type { $$ = $1; }
| '?' type { $$ = Node\NullableType[$2]; } | '?' type { $$ = Node\NullableType[$2]; }
| union_type { $$ = Node\UnionType[$1]; } | union_type { $$ = Node\UnionType[$1]; }
| intersection_type { $$ = Node\IntersectionType[$1]; } | intersection_type { $$ = $1; }
; ;
type: type:
@@ -575,34 +595,52 @@ type_without_static:
| T_CALLABLE { $$ = Node\Identifier['callable']; } | T_CALLABLE { $$ = Node\Identifier['callable']; }
; ;
union_type_element:
type { $$ = $1; }
| '(' intersection_type ')' { $$ = $2; }
;
union_type: union_type:
type '|' type { init($1, $3); } union_type_element '|' union_type_element { init($1, $3); }
| union_type '|' type { push($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: union_type_without_static:
type_without_static '|' type_without_static { init($1, $3); } union_type_without_static_element '|' union_type_without_static_element { init($1, $3); }
| union_type_without_static '|' type_without_static { push($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: intersection_type:
type T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG type { init($1, $3); } intersection_type_list { $$ = Node\IntersectionType[$1]; }
| intersection_type T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG type ;
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); } { push($1, $3); }
; ;
intersection_type_without_static: intersection_type_without_static:
type_without_static T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG type_without_static intersection_type_without_static_list { $$ = Node\IntersectionType[$1]; }
{ init($1, $3); }
| intersection_type_without_static T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG type_without_static
{ push($1, $3); }
; ;
type_expr_without_static: type_expr_without_static:
type_without_static { $$ = $1; } type_without_static { $$ = $1; }
| '?' type_without_static { $$ = Node\NullableType[$2]; } | '?' type_without_static { $$ = Node\NullableType[$2]; }
| union_type_without_static { $$ = Node\UnionType[$1]; } | union_type_without_static { $$ = Node\UnionType[$1]; }
| intersection_type_without_static { $$ = Node\IntersectionType[$1]; } | intersection_type_without_static { $$ = $1; }
; ;
optional_type_without_static: optional_type_without_static:
@@ -934,8 +972,13 @@ lexical_var:
optional_ref plain_variable { $$ = Expr\ClosureUse[$2, $1]; } optional_ref plain_variable { $$ = Expr\ClosureUse[$2, $1]; }
; ;
name_readonly:
T_READONLY { $$ = Name[$1]; }
;
function_call: function_call:
name argument_list { $$ = Expr\FuncCall[$1, $2]; } name argument_list { $$ = Expr\FuncCall[$1, $2]; }
| name_readonly argument_list { $$ = Expr\FuncCall[$1, $2]; }
| callable_expr 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 | class_name_or_var T_PAAMAYIM_NEKUDOTAYIM member_name argument_list
{ $$ = Expr\StaticCall[$1, $3, $4]; } { $$ = Expr\StaticCall[$1, $3, $4]; }
@@ -1014,9 +1057,7 @@ dereferencable_scalar:
{ $attrs = attributes(); $attrs['kind'] = Expr\Array_::KIND_LONG; { $attrs = attributes(); $attrs['kind'] = Expr\Array_::KIND_LONG;
$$ = new Expr\Array_($3, $attrs); } $$ = new Expr\Array_($3, $attrs); }
| array_short_syntax { $$ = $1; } | array_short_syntax { $$ = $1; }
| T_CONSTANT_ENCAPSED_STRING | T_CONSTANT_ENCAPSED_STRING { $$ = Scalar\String_::fromString($1, attributes()); }
{ $attrs = attributes(); $attrs['kind'] = strKind($1);
$$ = new Scalar\String_(Scalar\String_::parse($1), $attrs); }
| '"' encaps_list '"' | '"' encaps_list '"'
{ $attrs = attributes(); $attrs['kind'] = Scalar\String_::KIND_DOUBLE_QUOTED; { $attrs = attributes(); $attrs['kind'] = Scalar\String_::KIND_DOUBLE_QUOTED;
parseEncapsed($2, '"', true); $$ = new Scalar\Encapsed($2, $attrs); } parseEncapsed($2, '"', true); $$ = new Scalar\Encapsed($2, $attrs); }
@@ -1024,7 +1065,7 @@ dereferencable_scalar:
scalar: scalar:
T_LNUMBER { $$ = $this->parseLNumber($1, attributes()); } T_LNUMBER { $$ = $this->parseLNumber($1, attributes()); }
| T_DNUMBER { $$ = Scalar\DNumber[Scalar\DNumber::parse($1)]; } | T_DNUMBER { $$ = Scalar\DNumber::fromString($1, attributes()); }
| dereferencable_scalar { $$ = $1; } | dereferencable_scalar { $$ = $1; }
| constant { $$ = $1; } | constant { $$ = $1; }
| class_constant { $$ = $1; } | class_constant { $$ = $1; }

View File

@@ -128,14 +128,6 @@ function resolveMacros($code) {
. ' else { ' . $args[0] . ' = null; }'; . ' else { ' . $args[0] . ' = null; }';
} }
if ('strKind' === $name) {
assertArgs(1, $args, $name);
return '(' . $args[0] . '[0] === "\'" || (' . $args[0] . '[1] === "\'" && '
. '(' . $args[0] . '[0] === \'b\' || ' . $args[0] . '[0] === \'B\')) '
. '? Scalar\String_::KIND_SINGLE_QUOTED : Scalar\String_::KIND_DOUBLE_QUOTED)';
}
if ('prependLeadingComments' === $name) { if ('prependLeadingComments' === $name) {
assertArgs(1, $args, $name); assertArgs(1, $args, $name);

View File

@@ -67,7 +67,7 @@ class Class_ extends Declaration
* @return $this The builder instance (for fluid interface) * @return $this The builder instance (for fluid interface)
*/ */
public function makeAbstract() { public function makeAbstract() {
$this->flags = BuilderHelpers::addModifier($this->flags, Stmt\Class_::MODIFIER_ABSTRACT); $this->flags = BuilderHelpers::addClassModifier($this->flags, Stmt\Class_::MODIFIER_ABSTRACT);
return $this; return $this;
} }
@@ -78,7 +78,13 @@ class Class_ extends Declaration
* @return $this The builder instance (for fluid interface) * @return $this The builder instance (for fluid interface)
*/ */
public function makeFinal() { public function makeFinal() {
$this->flags = BuilderHelpers::addModifier($this->flags, Stmt\Class_::MODIFIER_FINAL); $this->flags = BuilderHelpers::addClassModifier($this->flags, Stmt\Class_::MODIFIER_FINAL);
return $this;
}
public function makeReadonly() {
$this->flags = BuilderHelpers::addClassModifier($this->flags, Stmt\Class_::MODIFIER_READONLY);
return $this; return $this;
} }

View File

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

View File

@@ -178,7 +178,20 @@ final class BuilderHelpers
} }
$builtinTypes = [ $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); $lowerType = strtolower($type);
@@ -310,4 +323,13 @@ final class BuilderHelpers
Stmt\Class_::verifyModifier($modifiers, $modifier); Stmt\Class_::verifyModifier($modifiers, $modifier);
return $modifiers | $modifier; return $modifiers | $modifier;
} }
/**
* Adds a modifier and returns new modifier bitmask.
* @return int New modifiers
*/
public static function addClassModifier(int $existingModifiers, int $modifierToSet) : int {
Stmt\Class_::verifyClassModifier($existingModifiers, $modifierToSet);
return $existingModifiers | $modifierToSet;
}
} }

View File

@@ -206,6 +206,11 @@ class TokenStream
|| $this->haveTokenInRange($startPos, $endPos, '}'); || $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. * Get indentation before token position.
* *

View File

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

View File

@@ -33,7 +33,7 @@ abstract class KeywordEmulator extends TokenEmulator
/** /**
* @param mixed[] $tokens * @param mixed[] $tokens
* @return mixed[]|null * @return array|string|null
*/ */
private function getPreviousNonSpaceToken(array $tokens, int $start) private function getPreviousNonSpaceToken(array $tokens, int $start)
{ {

View File

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

View File

@@ -20,4 +20,17 @@ final class ReadonlyTokenEmulator extends KeywordEmulator
{ {
return \T_READONLY; return \T_READONLY;
} }
protected function isKeywordContext(array $tokens, int $pos): bool
{
if (!parent::isKeywordContext($tokens, $pos)) {
return false;
}
// Support "function readonly("
return !(isset($tokens[$pos + 1]) &&
($tokens[$pos + 1][0] === '(' ||
($tokens[$pos + 1][0] === \T_WHITESPACE &&
isset($tokens[$pos + 2]) &&
$tokens[$pos + 2][0] === '(')));
}
} }

View File

@@ -11,7 +11,7 @@ class Const_ extends NodeAbstract
/** @var Expr Value */ /** @var Expr Value */
public $value; public $value;
/** @var Name Namespaced name (if using NameResolver) */ /** @var Name|null Namespaced name (if using NameResolver) */
public $namespacedName; public $namespacedName;
/** /**

View File

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

View File

@@ -24,6 +24,17 @@ class DNumber extends Scalar
return ['value']; return ['value'];
} }
/**
* @param mixed[] $attributes
*/
public static function fromString(string $str, array $attributes = []): DNumber
{
$attributes['rawValue'] = $str;
$float = self::parse($str);
return new DNumber($float, $attributes);
}
/** /**
* @internal * @internal
* *
@@ -36,13 +47,7 @@ class DNumber extends Scalar
public static function parse(string $str) : float { public static function parse(string $str) : float {
$str = str_replace('_', '', $str); $str = str_replace('_', '', $str);
// if string contains any of .eE just cast it to float // Check whether this is one of the special integer notations.
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
if ('0' === $str[0]) { if ('0' === $str[0]) {
// hex // hex
if ('x' === $str[1] || 'X' === $str[1]) { if ('x' === $str[1] || 'X' === $str[1]) {
@@ -54,10 +59,12 @@ class DNumber extends Scalar
return bindec($str); return bindec($str);
} }
// oct // oct, but only if the string does not contain any of '.eE'.
// substr($str, 0, strcspn($str, '89')) cuts the string at the first invalid digit (8 or 9) if (false === strpbrk($str, '.eE')) {
// so that only the digits before that are used // substr($str, 0, strcspn($str, '89')) cuts the string at the first invalid digit
return octdec(substr($str, 0, strcspn($str, '89'))); // (8 or 9) so that only the digits before that are used.
return octdec(substr($str, 0, strcspn($str, '89')));
}
} }
// dec // dec

View File

@@ -41,6 +41,8 @@ class LNumber extends Scalar
* @return LNumber The constructed LNumber, including kind attribute * @return LNumber The constructed LNumber, including kind attribute
*/ */
public static function fromString(string $str, array $attributes = [], bool $allowInvalidOctal = false) : LNumber { public static function fromString(string $str, array $attributes = [], bool $allowInvalidOctal = false) : LNumber {
$attributes['rawValue'] = $str;
$str = str_replace('_', '', $str); $str = str_replace('_', '', $str);
if ('0' !== $str[0] || '0' === $str) { if ('0' !== $str[0] || '0' === $str) {

View File

@@ -42,6 +42,22 @@ class String_ extends Scalar
return ['value']; return ['value'];
} }
/**
* @param bool $parseUnicodeEscape Whether to parse PHP 7 \u escapes
*/
public static function fromString(string $str, array $attributes = [], bool $parseUnicodeEscape = true): self
{
$attributes['kind'] = ($str[0] === "'" || ($str[1] === "'" && ($str[0] === 'b' || $str[0] === 'B')))
? Scalar\String_::KIND_SINGLE_QUOTED
: Scalar\String_::KIND_DOUBLE_QUOTED;
$attributes['rawValue'] = $str;
$string = self::parse($str, $parseUnicodeEscape);
return new self($string, $attributes);
}
/** /**
* @internal * @internal
* *

View File

@@ -13,7 +13,7 @@ abstract class ClassLike extends Node\Stmt
/** @var Node\AttributeGroup[] PHP attribute groups */ /** @var Node\AttributeGroup[] PHP attribute groups */
public $attrGroups; public $attrGroups;
/** @var Node\Name Namespaced name (if using NameResolver) */ /** @var Node\Name|null Namespaced name (if using NameResolver) */
public $namespacedName; public $namespacedName;
/** /**

View File

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

View File

@@ -68,6 +68,10 @@ class Class_ extends ClassLike
return (bool) ($this->flags & self::MODIFIER_FINAL); return (bool) ($this->flags & self::MODIFIER_FINAL);
} }
public function isReadonly() : bool {
return (bool) ($this->flags & self::MODIFIER_READONLY);
}
/** /**
* Whether the class is anonymous. * Whether the class is anonymous.
* *
@@ -77,6 +81,27 @@ class Class_ extends ClassLike
return null === $this->name; return null === $this->name;
} }
/**
* @internal
*/
public static function verifyClassModifier($a, $b) {
if ($a & self::MODIFIER_ABSTRACT && $b & self::MODIFIER_ABSTRACT) {
throw new Error('Multiple abstract modifiers are not allowed');
}
if ($a & self::MODIFIER_FINAL && $b & self::MODIFIER_FINAL) {
throw new Error('Multiple final modifiers are not allowed');
}
if ($a & self::MODIFIER_READONLY && $b & self::MODIFIER_READONLY) {
throw new Error('Multiple readonly modifiers are not allowed');
}
if ($a & 48 && $b & 48) {
throw new Error('Cannot use the final modifier on an abstract class');
}
}
/** /**
* @internal * @internal
*/ */

View File

@@ -20,7 +20,7 @@ class Function_ extends Node\Stmt implements FunctionLike
/** @var Node\AttributeGroup[] PHP attribute groups */ /** @var Node\AttributeGroup[] PHP attribute groups */
public $attrGroups; public $attrGroups;
/** @var Node\Name Namespaced name (if using NameResolver) */ /** @var Node\Name|null Namespaced name (if using NameResolver) */
public $namespacedName; public $namespacedName;
/** /**

View File

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

View File

@@ -161,7 +161,7 @@ class NameResolver extends NodeVisitorAbstract
return null; 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 // Add prefix for group uses
$name = $prefix ? Name::concat($prefix, $use->name) : $use->name; $name = $prefix ? Name::concat($prefix, $use->name) : $use->name;
// Type is determined either by individual element or whole use declaration // 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

View File

@@ -16,9 +16,12 @@ use PhpParser\Node\Scalar\String_;
use PhpParser\Node\Stmt\Class_; use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\ClassConst; use PhpParser\Node\Stmt\ClassConst;
use PhpParser\Node\Stmt\ClassMethod; use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\Node\Stmt\Else_;
use PhpParser\Node\Stmt\ElseIf_;
use PhpParser\Node\Stmt\Enum_; use PhpParser\Node\Stmt\Enum_;
use PhpParser\Node\Stmt\Interface_; use PhpParser\Node\Stmt\Interface_;
use PhpParser\Node\Stmt\Namespace_; use PhpParser\Node\Stmt\Namespace_;
use PhpParser\Node\Stmt\Nop;
use PhpParser\Node\Stmt\Property; use PhpParser\Node\Stmt\Property;
use PhpParser\Node\Stmt\TryCatch; use PhpParser\Node\Stmt\TryCatch;
use PhpParser\Node\Stmt\UseUse; use PhpParser\Node\Stmt\UseUse;
@@ -664,6 +667,7 @@ abstract class ParserAbstract implements Parser
'false' => true, 'false' => true,
'mixed' => true, 'mixed' => true,
'never' => true, 'never' => true,
'true' => true,
]; ];
if (!$name->isUnqualified()) { if (!$name->isUnqualified()) {
@@ -875,6 +879,33 @@ abstract class ParserAbstract implements Parser
return $attributes; 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);
} catch (Error $error) {
$error->setAttributes($this->getAttributesAt($modifierPos));
$this->emitError($error);
}
}
protected function checkModifier($a, $b, $modifierPos) { protected function checkModifier($a, $b, $modifierPos) {
// Jumping through some hoops here because verifyModifier() is also used elsewhere // Jumping through some hoops here because verifyModifier() is also used elsewhere
try { try {

View File

@@ -46,7 +46,15 @@ class Standard extends PrettyPrinterAbstract
} }
protected function pUnionType(Node\UnionType $node) { 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) { protected function pIntersectionType(Node\IntersectionType $node) {

View File

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

View File

@@ -68,6 +68,20 @@ class ClassTest extends \PHPUnit\Framework\TestCase
); );
} }
public function testReadonly() {
$node = $this->createClassBuilder('Test')
->makeReadonly()
->getNode()
;
$this->assertEquals(
new Stmt\Class_('Test', [
'flags' => Stmt\Class_::MODIFIER_READONLY
]),
$node
);
}
public function testStatementOrder() { public function testStatementOrder() {
$method = new Stmt\ClassMethod('testMethod'); $method = new Stmt\ClassMethod('testMethod');
$property = new Stmt\Property( $property = new Stmt\Property(

View File

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

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('iterable'), BuilderHelpers::normalizeType('iterable'));
$this->assertEquals(new Node\Identifier('void'), BuilderHelpers::normalizeType('void')); $this->assertEquals(new Node\Identifier('void'), BuilderHelpers::normalizeType('void'));
$this->assertEquals(new Node\Identifier('object'), BuilderHelpers::normalizeType('object')); $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('mixed'), BuilderHelpers::normalizeType('mixed'));
$this->assertEquals(new Node\Identifier('never'), BuilderHelpers::normalizeType('never')); $this->assertEquals(new Node\Identifier('never'), BuilderHelpers::normalizeType('never'));
$this->assertEquals(new Node\Identifier('true'), BuilderHelpers::normalizeType('true'));
$intIdentifier = new Node\Identifier('int'); $intIdentifier = new Node\Identifier('int');
$this->assertSame($intIdentifier, BuilderHelpers::normalizeType($intIdentifier)); $this->assertSame($intIdentifier, BuilderHelpers::normalizeType($intIdentifier));

View File

@@ -241,8 +241,8 @@ class EmulativeTest extends LexerTest
['1_000', [ ['1_000', [
[Tokens::T_LNUMBER, '1_000'], [Tokens::T_LNUMBER, '1_000'],
]], ]],
['0xCAFE_F00D', [ ['0x7AFE_F00D', [
[Tokens::T_LNUMBER, '0xCAFE_F00D'], [Tokens::T_LNUMBER, '0x7AFE_F00D'],
]], ]],
['0b0101_1111', [ ['0b0101_1111', [
[Tokens::T_LNUMBER, '0b0101_1111'], [Tokens::T_LNUMBER, '0b0101_1111'],
@@ -354,6 +354,20 @@ class EmulativeTest extends LexerTest
['0o1000000000000000000000', [ ['0o1000000000000000000000', [
[Tokens::T_DNUMBER, '0o1000000000000000000000'], [Tokens::T_DNUMBER, '0o1000000000000000000000'],
]], ]],
['readonly class', [
[Tokens::T_READONLY, 'readonly'],
[Tokens::T_CLASS, 'class'],
]],
['function readonly(', [
[Tokens::T_FUNCTION, 'function'],
[Tokens::T_READONLY, 'readonly'],
[ord('('), '('],
]],
['function readonly (', [
[Tokens::T_FUNCTION, 'function'],
[Tokens::T_READONLY, 'readonly'],
[ord('('), '('],
]],
]; ];
} }

View File

@@ -73,6 +73,12 @@ class NameTest extends \PHPUnit\Framework\TestCase
(new Name('foo\bar\baz'))->slice(0, -4); (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() { public function testConcat() {
$this->assertEquals(new Name('foo\bar\baz'), Name::concat('foo', 'bar\baz')); $this->assertEquals(new Name('foo\bar\baz'), Name::concat('foo', 'bar\baz'));
$this->assertEquals( $this->assertEquals(

View File

@@ -0,0 +1,27 @@
<?php
declare(strict_types=1);
namespace PhpParser\Node\Scalar;
use PhpParser\Node\Stmt\Echo_;
use PhpParser\ParserFactory;
class DNumberTest extends \PHPUnit\Framework\TestCase
{
public function testRawValue()
{
$parser = (new ParserFactory())->create(ParserFactory::PREFER_PHP7);
$nodes = $parser->parse('<?php echo 1_234.56;');
$echo = $nodes[0];
$this->assertInstanceOf(Echo_::class, $echo);
/** @var Echo_ $echo */
$lLumber = $echo->exprs[0];
$this->assertInstanceOf(DNumber::class, $lLumber);
/** @var DNumber $dnumber */
$this->assertSame(1234.56, $lLumber->value);
$this->assertSame('1_234.56', $lLumber->getAttribute('rawValue'));
}
}

View File

@@ -0,0 +1,26 @@
<?php declare(strict_types=1);
namespace PhpParser\Node\Scalar;
use PhpParser\Node\Stmt\Echo_;
use PhpParser\ParserFactory;
class NumberTest extends \PHPUnit\Framework\TestCase
{
public function testRawValue()
{
$parser = (new ParserFactory())->create(ParserFactory::PREFER_PHP7);
$nodes = $parser->parse('<?php echo 1_234;');
$echo = $nodes[0];
$this->assertInstanceOf(Echo_::class, $echo);
/** @var Echo_ $echo */
$lLumber = $echo->exprs[0];
$this->assertInstanceOf(LNumber::class, $lLumber);
/** @var LNumber $lnumber */
$this->assertSame(1234, $lLumber->value);
$this->assertSame('1_234', $lLumber->getAttribute('rawValue'));
}
}

View File

@@ -2,8 +2,28 @@
namespace PhpParser\Node\Scalar; namespace PhpParser\Node\Scalar;
use PhpParser\Node\Stmt\Echo_;
use PhpParser\ParserFactory;
class StringTest extends \PHPUnit\Framework\TestCase class StringTest extends \PHPUnit\Framework\TestCase
{ {
public function testRawValue()
{
$parser = (new ParserFactory())->create(ParserFactory::PREFER_PHP7);
$nodes = $parser->parse('<?php echo "sequence \x41";');
$echo = $nodes[0];
$this->assertInstanceOf(Echo_::class, $echo);
/** @var Echo_ $echo */
$string = $echo->exprs[0];
$this->assertInstanceOf(String_::class, $string);
/** @var String_ $string */
$this->assertSame('sequence A', $string->value);
$this->assertSame('"sequence \\x41"', $string->getAttribute('rawValue'));
}
/** /**
* @dataProvider provideTestParseEscapeSequences * @dataProvider provideTestParseEscapeSequences
*/ */

View File

@@ -245,6 +245,7 @@ PHP;
"attributes": { "attributes": {
"startLine": 4, "startLine": 4,
"endLine": 4, "endLine": 4,
"rawValue": "0",
"kind": 10 "kind": 10
} }
}, },
@@ -273,7 +274,8 @@ PHP;
"value": 1, "value": 1,
"attributes": { "attributes": {
"startLine": 4, "startLine": 4,
"endLine": 4 "endLine": 4,
"rawValue": "1.0"
} }
}, },
"flags": 0, "flags": 0,
@@ -295,7 +297,8 @@ PHP;
"attributes": { "attributes": {
"startLine": 5, "startLine": 5,
"endLine": 5, "endLine": 5,
"kind": 1 "kind": 1,
"rawValue": "'Foo'"
} }
} }
], ],
@@ -398,6 +401,7 @@ JSON;
"attributes": { "attributes": {
"startLine": 4, "startLine": 4,
"endLine": 4, "endLine": 4,
"rawValue": "0",
"kind": 10 "kind": 10
}, },
"value": 0 "value": 0
@@ -426,7 +430,8 @@ JSON;
"nodeType": "Scalar_DNumber", "nodeType": "Scalar_DNumber",
"attributes": { "attributes": {
"startLine": 4, "startLine": 4,
"endLine": 4 "endLine": 4,
"rawValue": "1.0"
}, },
"value": 1 "value": 1
}, },
@@ -448,7 +453,8 @@ JSON;
"attributes": { "attributes": {
"startLine": 5, "startLine": 5,
"endLine": 5, "endLine": 5,
"kind": 1 "kind": 1,
"rawValue": "'Foo'"
}, },
"value": "Foo" "value": "Foo"
} }

View File

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

View File

@@ -42,13 +42,71 @@ function test() {
baz(); baz();
} }
----- -----
// TODO Fix broken result // TODO Preserve formatting
$stmts[0]->stmts[1] = $stmts[0]->stmts[2]; $stmts[0]->stmts[1] = $stmts[0]->stmts[2];
----- -----
<?php <?php
function test() { function test()
foo();<?php {
foo();
baz(); baz();
baz(); baz();
} }
-----
<?php
function test() {
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) // (all are actually the same number, just in different representations)
18446744073709551615; 18446744073709551615;
0xFFFFFFFFFFFFFFFF; 0xFFFFFFFFFFFFFFFF;
0xEEEEEEEEEEEEEEEE;
01777777777777777777777; 01777777777777777777777;
0177777777777777777777787; 0177777777777777777777787;
0b1111111111111111111111111111111111111111111111111111111111111111; 0b1111111111111111111111111111111111111111111111111111111111111111;
@@ -92,7 +93,7 @@ array(
) )
12: Stmt_Expression( 12: Stmt_Expression(
expr: Scalar_DNumber( expr: Scalar_DNumber(
value: 1.844674407371E+19 value: 1.7216961135462E+19
) )
) )
13: Stmt_Expression( 13: Stmt_Expression(
@@ -105,4 +106,9 @@ array(
value: 1.844674407371E+19 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; 6.674_083e-11;
299_792_458; 299_792_458;
0xCAFE_F00D; 0x7AFE_F00D;
0b0101_1111; 0b0101_1111;
0137_041; 0137_041;
@@ -42,7 +42,7 @@ array(
) )
2: Stmt_Expression( 2: Stmt_Expression(
expr: Scalar_LNumber( expr: Scalar_LNumber(
value: 3405705229 value: 2063527949
) )
) )
3: Stmt_Expression( 3: Stmt_Expression(

View File

@@ -66,7 +66,7 @@ array(
) )
) )
----- -----
<?php class A { readonly readonly $a; } <?php class C { readonly readonly $a; }
----- -----
!!php7 !!php7
Multiple readonly modifiers are not allowed from 1:26 to 1:33 Multiple readonly modifiers are not allowed from 1:26 to 1:33
@@ -76,7 +76,7 @@ array(
) )
flags: 0 flags: 0
name: Identifier( name: Identifier(
name: A name: C
) )
extends: null extends: null
implements: array( implements: array(
@@ -231,8 +231,29 @@ array(
) )
----- -----
<?php abstract final class A { } <?php abstract final class A { }
-----
!!php7
Cannot use the final modifier on an abstract class from 1:16 to 1:20
array(
0: Stmt_Class(
attrGroups: array(
)
flags: MODIFIER_ABSTRACT | MODIFIER_FINAL (48)
name: Identifier(
name: A
)
extends: null
implements: array(
)
stmts: array(
)
)
)
-----
<?php abstract final class A { }
// Type in the partial parse could conceivably be any of 0, 16 or 32 // Type in the partial parse could conceivably be any of 0, 16 or 32
----- -----
!!php5
Syntax error, unexpected T_FINAL, expecting T_CLASS from 1:16 to 1:20 Syntax error, unexpected T_FINAL, expecting T_CLASS from 1:16 to 1:20
array( array(
0: Stmt_Class( 0: Stmt_Class(
@@ -258,7 +279,8 @@ array(
<?php readonly class A { } <?php readonly class A { }
// Type in the partial parse could conceivably be any of 0, 16 or 32 // Type in the partial parse could conceivably be any of 0, 16 or 32
----- -----
Syntax error, unexpected T_READONLY from 1:7 to 1:14 !!php5
Syntax error, unexpected T_CLASS, expecting '(' from 1:16 to 1:20
array( array(
0: Stmt_Class( 0: Stmt_Class(
attrGroups: array( attrGroups: array(
@@ -280,7 +302,7 @@ array(
) )
) )
----- -----
<?php class A { abstract $a; } <?php class B { abstract $b; }
----- -----
Properties cannot be declared abstract from 1:17 to 1:24 Properties cannot be declared abstract from 1:17 to 1:24
array( array(
@@ -289,7 +311,7 @@ array(
) )
flags: 0 flags: 0
name: Identifier( name: Identifier(
name: A name: B
) )
extends: null extends: null
implements: array( implements: array(
@@ -303,7 +325,7 @@ array(
props: array( props: array(
0: Stmt_PropertyProperty( 0: Stmt_PropertyProperty(
name: VarLikeIdentifier( name: VarLikeIdentifier(
name: a name: b
) )
default: null default: null
) )

View File

@@ -0,0 +1,68 @@
Readonly class
-----
<?php
readonly class A {
}
-----
!!php7
array(
0: Stmt_Class(
attrGroups: array(
)
flags: MODIFIER_READONLY (64)
name: Identifier(
name: A
)
extends: null
implements: array(
)
stmts: array(
)
)
)
-----
<?php
readonly class A {
}
-----
!!php5
Syntax error, unexpected T_CLASS, expecting '(' from 3:10 to 3:14
array(
0: Stmt_Class(
attrGroups: array(
)
flags: 0
name: Identifier(
name: A
)
extends: null
implements: array(
)
stmts: array(
)
)
)
-----
<?php
final readonly class A {
}
-----
!!php7
array(
0: Stmt_Class(
attrGroups: array(
)
flags: MODIFIER_FINAL | MODIFIER_READONLY (96)
name: Identifier(
name: A
)
extends: null
implements: array(
)
stmts: array(
)
)
)

View File

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

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

View File

@@ -65,3 +65,27 @@ function test()
{ {
// empty // 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
}
}

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

View File

@@ -0,0 +1,12 @@
Readonly class
-----
<?php
readonly class Foo
{
}
-----
!!php7
readonly class Foo
{
}

View File

@@ -98,7 +98,9 @@ switch ($testType) {
# pretty print difference due to INF vs 1e1000 # pretty print difference due to INF vs 1e1000
| ext.standard.tests.general_functions.bug27678 | ext.standard.tests.general_functions.bug27678
| tests.lang.bug24640 | tests.lang.bug24640
| tests.lang.integer_literals.(binary|octal|hexadecimal)_(32|64)bit
| Zend.tests.bug74947 | Zend.tests.bug74947
| Zend.tests.float_to_int.union_int_string_type_arg
# pretty print differences due to negative LNumbers # pretty print differences due to negative LNumbers
| Zend.tests.neg_num_string | Zend.tests.neg_num_string
| Zend.tests.numeric_strings.neg_num_string | Zend.tests.numeric_strings.neg_num_string