1
0
mirror of https://github.com/nikic/PHP-Parser.git synced 2025-07-25 16:21:23 +02:00

Compare commits

...

123 Commits

Author SHA1 Message Date
Nikita Popov
c724dde741 Allow final on promoted properties 2025-07-20 21:13:06 +02:00
Nikita Popov
7c4f7ca987 Exclude one clone php-src test
clone($x, ) will parse as a function call, but be printed as
clone($x), which parses as a clone expression. The difference is
harmless.
2025-07-20 21:08:23 +02:00
Nikita Popov
c5216ac220 Remove use of E_STRICT in test runner
This is deprecated in PHP 8.4.
2025-07-20 20:56:38 +02:00
Nikita Popov
507fa7632e Add support for void cast
We model this as an expression, just like all the other casts, even
though PHP treats (void) specially and makes it a statement. However,
it has a peculiar carve-out where (void) *is* allowed in for init
and loop expression lists (which only accept expressions, not
statements). As such, we need to model it as an expression.
2025-07-20 20:52:42 +02:00
Nikita Popov
3e74153456 Add emulation support for void cast 2025-07-20 20:20:03 +02:00
Nikita Popov
b815a165bd Add support for pipe operator 2025-07-20 18:50:46 +02:00
Nikita Popov
c1f6c4c8d8 Add lexer emulation support for pipe operator 2025-07-20 18:27:01 +02:00
Caleb White
66d5018bb7 feat: add CallLike::getArg() method ()
This method returns the named argument that matches the given `$name`,
or the positional (unnamed) argument that exists at the given `$position`,
otherwise, returns `null` for first-class callables or if no match is found.
2025-07-19 21:41:11 +02:00
Tim Düsterhus
dc47765670 Support every argument syntax for clone() ()
See 
2025-07-02 22:09:49 +02:00
Tim Düsterhus
8ab65b4adc Support declaring functions with name clone ()
For use in stubs.

see 9c7a3f8d8f
see https://wiki.php.net/rfc/clone_with_v2
see 
2025-06-23 14:35:24 +02:00
Brian Henry
7fc3bcf970 Minor spelling now longer -> no longer () 2025-06-01 10:25:00 +02:00
Brian Henry
acf8f5ef01 Minor spelling creatForHostVersion() -> createForHostVersion() () 2025-06-01 10:24:40 +02:00
Nikita Popov
ae59794362 Release PHP-Parser 5.5.0 2025-05-31 10:24:38 +02:00
Daniel Scherzer
4964f8d5dd Add support for attributes on constants
Just merged in php/php-src@3f03f7ed3d, updating
this parser is needed to be able to use attributes on constants in the stub
files.
2025-04-29 23:19:04 +02:00
Nikita Popov
20b0d55f66 Use multi-line attributes for params when targeting PHP <8.0
When the pretty printer targets a version older than PHP 8,
print the attributes for parameters on a separate line, so that
they are interpreted as comments.

Fixes https://github.com/nikic/PHP-Parser/issues/1081.
2025-04-24 22:08:26 +02:00
Markus Staab
c9d0b6c9f2 Support WeakReferences in NodeConnectingVisitor ()
Add a constructor argument which enables the use of WeakReferences.
The attributes are called weak_parent, weak_next and weak_previous
in that case.
2025-02-26 20:51:23 +01:00
Markus Staab
14f9c9df7f Use more precise list-types for getter methods 2025-02-18 22:17:03 +01:00
Abdul Malik Ikhsan
fa02db3f30 Remove useless foreach key and cast string, and fix invalid @var definition in test () 2025-01-17 22:41:39 +01:00
Abdul Malik Ikhsan
d4fce83c2c Remove useless cast (string) and @var doc 2025-01-16 21:27:18 +01:00
Nikita Popov
7d3039c378 Update to phpstan 2.0 2024-12-30 12:14:29 +01:00
Nikita Popov
447a020a1f Release PHP-Parser 5.4.0 2024-12-30 12:07:19 +01:00
Markus Staab
45f70ed80a Simplify NodeTraverser->traverseNode() 2024-12-30 11:10:07 +01:00
Markus Staab
74a361814d Simplify NodeTraverser->traverseArray() 2024-12-30 11:10:07 +01:00
Nikita Popov
6478c5ac53 Fix PropertyHook::getStmts() for set hook
Produce the correct desugaring if the propertyName attribute is
set, and set it in the parser. Otherwise throw an exception.

Fixes .
2024-12-27 16:56:00 +01:00
Nikita Popov
8bb415902e Fix cs 2024-12-27 16:33:38 +01:00
Nikita Popov
48fd76e542 Make PropertyHook::getStmts() less incorrect
Still missing the assignment to the correct property, but at
least we're not returning from a non-void function now...
2024-12-27 16:30:03 +01:00
Ondrej Mirtes
d20a197ca7 Emit error - Multiple properties cannot share the same hooks
Closes GH-1052.
2024-12-27 16:19:46 +01:00
Ondrej Mirtes
62dee28027 Rename ParserAbstract method checkPropertyHookList to checkEmptyPropertyHookList 2024-12-27 16:13:47 +01:00
Nikita Popov
b396e9e0d6 Add missing newline after usage message 2024-12-27 16:13:29 +01:00
Ondrej Mirtes
f212bb7afb Add PropertyHook::isFinal() helper method with tests 2024-12-13 23:15:12 +01:00
Ondrej Mirtes
f43324a074 Missing flags subNode description in PropertyHook constructor PHPDoc 2024-12-13 23:15:12 +01:00
Ondrej Mirtes
73b160f8c4 Add flags helper methods Property::isAbstract() and Property::isFinal() 2024-12-13 23:15:12 +01:00
Ondrej Mirtes
4f9dc8b0f5 Promoted properties with hooks do not need visibility modifier 2024-12-11 10:17:55 +01:00
Nathanael Esayeas
469377f4a8 Fix classmap authoritative autoloading ()
This patch resolves  by adding dummy class definitions
behind if (false) checks that are picked up the authoritative
autoloader.
2024-12-06 22:00:17 +01:00
Jorg Sowa
74d3f7fc24 Removed reudndant token generation 2024-11-24 09:54:02 +01:00
Jorg Sowa
25828ea952 Updated target.php to new PHP-Parser version 2024-11-24 09:54:02 +01:00
Jorg Sowa
05ed79595b Removed unrecheable statement 2024-11-19 21:17:27 +01:00
Markus Staab
954f7a411f More precise toLowerString() return type 2024-11-05 21:19:09 +01:00
Nikita Popov
8eea230464 Release PHP-Parser 5.3.1 2024-10-08 20:51:32 +02:00
Nikita Popov
9c7a3f8d8f Support declaring functions with name exit/die
For use in stubs.

Fixes .
2024-10-08 20:46:50 +02:00
Markus Staab
26573ea64f Makefile works on windows 2024-10-07 20:54:36 +02:00
Nikita Popov
3abf7425cd Release PHP-Parser 5.3.0 2024-09-29 15:56:26 +02:00
Nikita Popov
961f158f6d Avoid negative indendation in formatting-preserving printer
Fixes .
2024-09-29 15:34:59 +02:00
Nikita Popov
e50c67b7a9 Add basic support for tab indentation
Add a new "indent" option for the pretty printer, which can be
use to control the indentation width, or switch it to use tabs.

Tab width is currenlty hardcoded to 4, but also shouldn't matter
much.

Possibly the formatting-preserving printer should auto-detect
the indentation in the future.
2024-09-21 20:58:10 +02:00
Nikita Popov
26a0197186 Use PHP 8.4 for the PHP 7.4 integration test 2024-09-21 16:03:21 +02:00
Nikita Popov
aedfcc23cd Include trailing semicolon in GroupUse
Fixes https://github.com/nikic/PHP-Parser/issues/1026.
2024-09-21 15:55:21 +02:00
Nikita Popov
8d09ba87f4 Support PropertyHooks in NameResolver 2024-09-17 21:33:51 +02:00
Nikita Popov
23c79fbbfb Release PHP-Parser 5.2.0 2024-09-15 18:40:33 +02:00
Nikita Popov
b9c8374498 Run PHP 8.4 integration tests 2024-09-15 18:32:49 +02:00
Nikita Popov
d0826bd3e4 Add changelog entries 2024-09-15 18:28:36 +02:00
Nikita Popov
5ea6c2938b Exclude integration test 2024-09-05 21:41:03 +02:00
Nikita Popov
b493c51cce [8.4] Add support for asymmetric visibility modifiers
Represented using new PRIVATE_SET, PROTECTED_SET and PUBLIC_SET
bits in Modifiers.

RFC: https://wiki.php.net/rfc/asymmetric-visibility-v2
2024-09-05 21:38:16 +02:00
Nikita Popov
54139ca49b Disable fail-fast
PHP 8.4 currently fails because the version is installed version
is too old. Avoid cancelling other CI jobs because of that.
2024-09-01 12:48:37 +02:00
Nikita Popov
cde9bab3bb Add support for PHP 8.4 exit function
For backwards-compatibility, parse the simple single argument
case into an Exit_ node as previously. For more complex
expressions generate a function call.
2024-09-01 12:41:27 +02:00
Nikita Popov
018da15f3a Add token emulation support for asymmetric visibility modifiers 2024-08-31 20:41:07 +02:00
evenevent
ba14437165 chore: fix comment
Signed-off-by: evenevent <digping@icloud.com>
2024-08-15 09:22:56 +02:00
Nikita Popov
4a22c15169 Validate options in test_old
Regularly try to use this one with --version instead of
--php-version, which fails in a non-obvious way.
2024-08-11 17:08:58 +02:00
Nikita Popov
514f71024e Fix for new php-cs-fixer version 2024-08-11 17:03:56 +02:00
Ruud Kamphuis
6a97061265 Normalize enum value to ClassConstFetch
Fixes 

(cherry picked from commit 8a21ec3182)
2024-08-11 16:58:28 +02:00
Nikita Popov
e3f223f623 Fix expected json dump for old PHP versions 2024-07-28 19:05:16 +02:00
Nikita Popov
03caf4cc99 [8.4] Add support for property hooks
Add hooks subnode to Stmt\Property and Param, which contains an
array of PropertyHook.

The property hook support is considered experimental and subject
to change.

RFC: https://wiki.php.net/rfc/property-hooks
2024-07-28 19:02:18 +02:00
Nikita Popov
b11fc12cce Use ParserFactory in integration test
So we actually use the PHP 8 parser on new versions.
2024-07-28 18:11:46 +02:00
Nikita Popov
3beee6ec77 Add Modifiers::toString() helper
Convert an integer modifier into a string.
2024-07-28 17:09:14 +02:00
Nikita Popov
58de479119 Remove error for abstract/final properties
These are allowed since PHP 8.4, with the introduction of property
hooks. (Abstract properties require hooks, which is not validated
here.)
2024-07-21 18:59:18 +02:00
Nikita Popov
d3ae2ed679 Respect version mode line in pretty printer tests
By default parser uses newest supported and pretty printer uses
its default version. If version is specified, it's used for both
parser and printer. Additionally, parserVersion can be used to
specify a different version for parser and printer.
2024-07-21 18:23:53 +02:00
Nikita Popov
beba9c528f Remove support for alternative array syntax in PHP 8 parser
We cannot support both this syntax and property hooks. Drop
support for the alternative syntax in the PHP 8 parser. The
PHP 7 parser still supports it.
2024-07-19 22:29:35 +02:00
Nikita Popov
fadccead52 Avoid compatibilty token list in parser
Create the mapping using the entries in symbolToName, so we don't
need to repeat the compatibility tokens in another place.
2024-07-14 20:53:13 +02:00
Nikita Popov
d8235a2701 [8.4] Add support for __PROPERTY__ magic constant
Part of property hooks RFC.
2024-07-14 18:52:47 +02:00
Amirreza Nasiri
5973c30a46 Update JSON_representation.markdown
Fix the invalid JSON structure
2024-07-13 15:46:21 +02:00
Nikita Popov
b917ba7b9c Add PHP 8.4 to CI matrix
Not using it in the integration tests yet.
2024-07-13 11:11:15 +02:00
Nikita Popov
caf540443a Declare PHP 8.4 as the newest supported version
As the comment indicates, this includes partially supported
versions. PHP-Parser includes support for all parts of PHP 8.4
that have landed in php-src.
2024-07-13 11:10:30 +02:00
Nikita Popov
683130c2ff Release PHP-Parser 5.1.0 2024-07-01 22:03:41 +02:00
Nikita Popov
a894652a3d Fix ternary precedence printing
The precedence table set the LHS and RHS precedence for the
ternary to -1, which is the dummy value used for unary operators.
Instead, it should be the same as the operator precedence, as
the ternary is non-associative since PHP 8.

Fixes .
2024-07-01 21:51:42 +02:00
Jing Xu(RainX)
3ef0811e45 Fix a typo: Dereferencable to Dereferenceable 2024-06-12 20:31:58 +02:00
Jing Xu(RainX)
db1963f9ea Fix a typo: Dereferenceing to Dereferencing 2024-06-12 20:29:11 +02:00
Jorg Adam Sowa
daaadc3bae Adjust tests to be compatible with PHPUnit 10 ()
This avoids warnings on PHPUnit 10, without actually
switching to PHPUnit 10.
2024-06-03 08:24:19 +02:00
Nikita Popov
7b0384cdbe [8.4] Add support for new deref without parens
RFC: https://wiki.php.net/rfc/new_without_parentheses
2024-05-31 23:07:44 +02:00
Jorg Adam Sowa
d57da64d7c Add missing void return types () 2024-05-31 09:32:45 +02:00
Jorg Sowa
d327cf2acf Updated actions/checkout to v4 2024-05-21 18:12:19 +02:00
Markus Staab
c5ee33df86 Declare more precise phpdoc types () 2024-04-19 21:04:10 +09:00
Nikita Popov
46be4560c4 Use PHP 8.3 for more CI jobs 2024-03-17 11:24:36 +01:00
Markus Staab
f2e037f8ea Make phpunit fail on deprecation warnings ()
(cherry picked from commit 4d36e9c16f)
2024-03-17 10:06:45 +01:00
Nikita Popov
b43758e9e9 Remove PHPUnit 7 and 8
PHPUnit 9 supports all the PHP versions that we need. Also update
the PHPUnit config schema.
2024-03-17 09:31:05 +01:00
Markus Staab
09691fc86e Prevent off-by-one errors in line-number related methods 2024-03-13 20:24:50 +01:00
Nikita Popov
139676794d Release PHP-Parser 5.0.2 2024-03-05 21:51:40 +01:00
Nikita Popov
70c96493b4 Fix indentation detection after opening tag
Fixes .
2024-03-02 18:59:44 +01:00
Maarten Buis
ec02613432 Update PhpVersion::getNewestSupported() to PHP 8.3 2024-03-02 08:27:21 +01:00
Nikita Popov
af14fdb282 Avoid cyclic reference in parser
Pass $this as an explicit $self argument to the reduce callbacks,
so we can make them static. This avoids a circular reference in
the parser, so that it can be immediately destroyed when it goes
out of scope.

Fixes .
2024-02-21 21:13:45 +01:00
Nikita Popov
2218c2252c Release PHP-Parser 5.0.1 2024-02-21 20:24:10 +01:00
sasezaki
ce019e9ad7 Add dev tool files to export-ignore 2024-01-14 10:02:54 +01:00
Nikita Popov
ff095c3c65 Check for tokens with non-integer ID
Fixes .
2024-01-10 20:34:09 +01:00
Nikita Popov
d619c8b4e6 Fix typo in upgrading guide
Fixes .
2024-01-09 20:23:09 +01:00
Nikita Popov
eb036d5a09 Add instructions for adding new syntax support 2024-01-07 18:54:10 +01:00
Nikita Popov
4a21235f7e Update documentation links in README 2024-01-07 18:17:35 +01:00
Nikita Popov
fba1d621c0 Release PHP-Parser 5.0.0 2024-01-07 18:14:20 +01:00
Nikita Popov
5cc5a67004 Upgrading guide tweaks 2024-01-07 18:11:19 +01:00
Nikita Popov
f603e19336 Avoid PHPUnit deprecation warnings 2024-01-07 17:46:24 +01:00
Ondrej Mirtes
1eeeb2d525 Fix parent class of PropertyItem and UseItem 2023-12-22 19:57:32 +01:00
Nikita Popov
255000ad49 Release PHP-Parser 5.0.0rc1 2023-12-20 22:32:29 +01:00
Nikita Popov
f7d484aa0e Fix handling of empty input
Fixes .
2023-12-20 21:51:46 +01:00
Nikita Popov
f82a6365a5 Add upgrading nodes for changes since beta1
Fixes .
Fixes .
2023-12-20 21:36:53 +01:00
Ondřej Mirtes
13a41f05a7 Fix typos in UPGRADE-5.0.md 2023-12-17 21:03:53 +01:00
Ondrej Mirtes
f66650073c Fix NameResolver for class constant native type 2023-11-28 20:10:53 +01:00
Nikita Popov
d0b35126e7 Update CHANGELOG 2023-11-12 17:06:58 +01:00
Nikita Popov
3640d18b87 Remove ParserFactory::create()
Don't try to keep backwards-compatibility with the old factory
style, which doesn't map cleanly onto supported options (we only
have ONLY_PHP7/PREFER_PHP7, which should probably create a Php8
parser in terms of how they are used, but this would no longer
match their names).

Instead, I have backported the new createForNewestSupportedVersion()
and createForHostVersion() methods to PHP-Parser 4.
2023-11-12 16:52:30 +01:00
Théo FIDRY
b54302f363 build: Exclude grammar from export git artifact
(cherry picked from commit 402b6cf345)
2023-11-12 13:04:04 +01:00
Nikita Popov
acfccd9d74 Run with updated php-cs-fixer 2023-11-01 18:50:25 +01:00
Sandro Gehri
eabae1f7fa Fix typo 2023-11-01 18:45:07 +01:00
xjaja
8d50e9d066 Don't drop class statements before error ()
When encountering a null statement (indicating that an error occurred),
retain the preceding statements. These were accidentally dropped
previously.

(cherry picked from commit 54103d8387)
2023-10-07 12:22:01 +02:00
Abdul Malik Ikhsan
481fec47f4 Improve performance of find() and findFirst() when passed $nodes is empty array 2023-10-07 12:11:47 +02:00
Nikita Popov
4e27a17cd8 Use visitor to assign comments
This fixes the long-standing issue where a comment would get assigned
to all nodes with the same starting position, instead of only the
outer-most one.

Fixes .
2023-09-28 21:45:08 +02:00
Nikita Popov
de84f76766 Avoid by-reference iteration in NodeTraverser
No need to create reference wrappers around every element in the
array.
2023-09-26 20:25:37 +02:00
Nikita Popov
b4183c2b09 Remove Stmt\Throw
This was a backwards-compatibility shim for Expr\Throw.
2023-09-25 18:37:07 +02:00
Nikita Popov
a1ccf57727 Introduce Stmt\Block
Stmt\Block will be created for { $a; } style blocks, unless these
occur directly inside some structure that is usually combined
with a block.

For example if ($a) { $b; } will continue to use the old
representation (plain array in in If_::$stmts), but a free-standing
{ $b; } will become a Stmt\Block.

Fixes .
2023-09-24 21:13:01 +02:00
Nikita Popov
f5adbb5e3f Add rawValue to InterpolatedStringPart and doc strings
For doc strings, the rawValue (on either the String_ or
InterpolatedStringPrts) does not include the leading indentation
(which is available as docIndentation) or the trailing newline on
the last part.
2023-09-24 12:55:38 +02:00
Nikita Popov
5edc190bda Add support for dumping additional attributes 2023-09-23 21:36:57 +02:00
Nikita Popov
80851163a6 Assemble NodeDumper result in property
Append to a property instead of returning strings. This is
significantly faster when dumping large ASTs.

This also fixes an inconsistency where the indentation level for
strings and comments was off-by-one.
2023-09-23 20:38:53 +02:00
Nikita Popov
8f175fe7c8 Pass --dry-run to php-cs-fixer
Apparently it only returns the correct exit code with the
--dry-run option.
2023-09-23 18:38:52 +02:00
Nikita Popov
d8e8065313 Don't parse unicode escapes for PHP < 7.0
We still had the option for this but were hardcoding it to true.
Make it conditional on the PHP version instead.
2023-09-23 17:35:51 +02:00
RainX
f4961b89ac Add missing phpdoc annotations in AST nodes () 2023-09-18 21:16:54 +02:00
Nikita Popov
8b9488e1e6 Explicit test new ParserFactory methods 2023-09-17 20:42:25 +02:00
Nikita Popov
21fa9c98b3 Drop some @covers annotations 2023-09-17 20:28:02 +02:00
Nikita Popov
0b3c762939 Update docs table of contents 2023-09-17 20:26:35 +02:00
247 changed files with 11436 additions and 5595 deletions
.gitattributes
.github/workflows
.php-cs-fixer.dist.phpCHANGELOG.mdCONTRIBUTING.mdMakefileREADME.mdUPGRADE-5.0.mdcomposer.json
doc
grammar
lib/PhpParser
phpstan-baseline.neonphpunit.xml.dist
test
PhpParser
code
formatPreservation
parser
prettyPrinter
fixtures
test_old
tools

5
.gitattributes vendored

@@ -1,12 +1,17 @@
/.github export-ignore
/doc export-ignore
/grammar export-ignore
/test export-ignore
/test_old export-ignore
/tools export-ignore
.editorconfig export-ignore
.gitattributes export-ignore
.gitignore export-ignore
.php-cs-fixer.dist.php export-ignore
Makefile export-ignore
CHANGELOG.md export-ignore
CONTRIBUTING.md export-ignore
phpstan-baseline.neon export-ignore
phpstan.neon.dist export-ignore
phpunit.xml.dist export-ignore
UPGRADE-*.md export-ignore

@@ -10,7 +10,7 @@ jobs:
name: "PHP 7.4 Unit Tests (with coverage)"
steps:
- name: "Checkout"
uses: "actions/checkout@v3"
uses: "actions/checkout@v4"
- name: "Install PHP"
uses: "shivammathur/setup-php@v2"
with:
@@ -20,7 +20,7 @@ jobs:
- name: "Install dependencies"
run: |
composer require php-coveralls/php-coveralls:^2.2 --dev --no-update
composer update --no-progress --prefer-dist
COMPOSER_ROOT_VERSION=dev-master composer update --no-progress --prefer-dist
- name: "Tests"
run: "php vendor/bin/phpunit --coverage-clover build/logs/clover.xml"
- name: Coveralls
@@ -38,62 +38,67 @@ jobs:
- "8.1"
- "8.2"
- "8.3"
- "8.4"
fail-fast: false
steps:
- name: "Checkout"
uses: "actions/checkout@v3"
uses: "actions/checkout@v4"
- name: "Install PHP"
uses: "shivammathur/setup-php@v2"
with:
coverage: "none"
php-version: "${{ matrix.php-version }}"
ini-file: "development"
tools: composer:v2
- name: "Install dependencies"
run: "composer update --no-progress --prefer-dist ${{ matrix.flags }}"
run: "COMPOSER_ROOT_VERSION=dev-master composer update --no-progress --prefer-dist ${{ matrix.flags }}"
- name: "PHPUnit"
run: "php vendor/bin/phpunit"
test_old_73_80:
runs-on: "ubuntu-latest"
name: "PHP 7.4 Code on PHP 8.2 Integration Tests"
name: "PHP 7.4 Code on PHP 8.4 Integration Tests"
steps:
- name: "Checkout"
uses: "actions/checkout@v3"
uses: "actions/checkout@v4"
- name: "Install PHP"
uses: "shivammathur/setup-php@v2"
with:
coverage: "none"
php-version: "8.2"
php-version: "8.4"
ini-file: "development"
tools: composer:v2
- name: "Install PHP 8 dependencies"
run: "composer update --no-progress --prefer-dist"
run: "COMPOSER_ROOT_VERSION=dev-master composer update --no-progress --prefer-dist"
- name: "Tests"
run: "test_old/run-php-src.sh 7.4.33"
test_old_80_70:
runs-on: "ubuntu-latest"
name: "PHP 8.3 Code on PHP 7.4 Integration Tests"
name: "PHP 8.4 Code on PHP 7.4 Integration Tests"
steps:
- name: "Checkout"
uses: "actions/checkout@v3"
uses: "actions/checkout@v4"
- name: "Install PHP"
uses: "shivammathur/setup-php@v2"
with:
coverage: "none"
php-version: "7.4"
ini-file: "development"
tools: composer:v2
- name: "Install PHP 8 dependencies"
run: "composer update --no-progress --prefer-dist"
run: "COMPOSER_ROOT_VERSION=dev-master composer update --no-progress --prefer-dist"
- name: "Tests"
run: "test_old/run-php-src.sh 8.3.0RC2"
run: "test_old/run-php-src.sh 8.4.0beta5"
phpstan:
runs-on: "ubuntu-latest"
name: "PHPStan"
steps:
- name: "Checkout"
uses: "actions/checkout@v3"
uses: "actions/checkout@v4"
- name: "Install PHP"
uses: "shivammathur/setup-php@v2"
with:
coverage: "none"
php-version: "8.2"
php-version: "8.3"
tools: composer:v2
- name: "Install dependencies"
run: |
@@ -105,15 +110,15 @@ jobs:
name: "PHP-CS-Fixer"
steps:
- name: "Checkout"
uses: "actions/checkout@v3"
uses: "actions/checkout@v4"
- name: "Install PHP"
uses: "shivammathur/setup-php@v2"
with:
coverage: "none"
php-version: "8.2"
php-version: "8.3"
tools: composer:v2
- name: "Install dependencies"
run: |
cd tools && composer install
- name: "php-cs-fixer"
run: "php tools/vendor/bin/php-cs-fixer fix"
run: "php tools/vendor/bin/php-cs-fixer fix --dry-run"

@@ -21,11 +21,6 @@ return $config->setRiskyAllowed(true)
'declare_strict_types' => true,
// Keep argument formatting for now.
'method_argument_space' => ['on_multiline' => 'ignore'],
'binary_operator_spaces' => [
'default' => 'at_least_single_space',
// Work around https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues/7303.
'operators' => ['=' => null],
],
'phpdoc_align' => ['align' => 'left'],
'phpdoc_trim' => true,
'no_empty_phpdoc' => true,

@@ -1,3 +1,163 @@
Version 5.5.0 (2025-05-31)
--------------------------
### Added
* [8.5] Added support for attributes on constants. `Stmt\Const_` now has an `attrGroups` subnode.
* Added `weakReferences` option to `NodeConnectingVisitor` and `ParentConnectingVisitor`. This
will create the parent/next/prev references as WeakReferences, to avoid making the AST cyclic
and thus increasing GC pressure.
### Changed
* Attributes on parameters are now printed on separate lines if the pretty printer target version
is PHP 7.4 or older (which is the default). This allows them to be interpreted as comments,
instead of causing a parse error. Specify a target version of PHP 8.0 or newer to restore the
previous behavior.
Version 5.4.0 (2024-12-30)
--------------------------
### Added
* Added `Property::isAbstract()` and `Property::isFinal()` methods.
* Added `PropertyHook::isFinal()` method.
* Emit an error if property hook is used on declaration with multiple properties.
### Fixed
* Make legacy class aliases compatible with classmap-authoritative autoloader.
* `Param::isPromoted()` and `Param::isPublic()` now returns true for parameters that have property
hooks but no explicit visibility modifier.
* `PropertyHook::getStmts()` now correctly desugars short `set` hooks. `set => $value` will be
expanded to `set { $this->propertyName = $value; }`. This requires the `propertyName` attribute
on the hook to be set, which is now also set by the parser. If the attribute is not set,
`getStmts()` will throw an error for short set hooks, as it is not possible to produce a correct
desugaring.
Version 5.3.1 (2024-10-08)
--------------------------
### Added
* Added support for declaring functions with name `exit` or `die`, to allow their use in stubs.
Version 5.3.0 (2024-09-29)
--------------------------
### Added
* Added `indent` option to pretty printer, which can be used to specify the indentation to use
(defaulting to four spaces). This also allows using tab indentation.
### Fixed
* Resolve names in `PropertyHook`s in the `NameResolver`.
* Include the trailing semicolon inside `Stmt\GroupUse` nodes, making them consistent with
`Stmt\Use_` nodes.
* Fixed indentation sometimes becoming negative in formatting-preserving pretty printer, resulting
in `ValueError`s.
Version 5.2.0 (2024-09-15)
--------------------------
### Added
* [8.4] Added support for `__PROPERTY__` magic constant, represented using a
`Node\Scalar\MagicConst\Property` node.
* [8.4] Added support for property hooks, which are represented using a new `hooks` subnode on
`Node\Stmt\Property` and `Node\Param`, which contains an array of `Node\PropertyHook`.
* [8.4] Added support for asymmetric visibility modifiers. Property `flags` can now hold the
additional bits `Modifiers::PUBLIC_SET`, `Modifiers::PROTECTED_SET` and `Modifiers::PRIVATE_SET`.
* [8.4] Added support for generalized exit function. For backwards compatibility, exit without
argument or a single plain argument continues to use a `Node\Expr\Exit_` node. Otherwise (e.g.
if a named argument is used) it will be represented as a plain `Node\Expr\FuncCall`.
* Added support for passing enum values to various builder methods, like `BuilderFactory::val()`.
### Removed
* Removed support for alternative array syntax `$array{0}` from the PHP 8 parser. It is still
supported by the PHP 7 parser. This is necessary in order to support property hooks.
Version 5.1.0 (2024-07-01)
--------------------------
### Added
* [8.4] Added support for dereferencing `new` expressions without parentheses.
### Fixed
* Fixed redundant parentheses being added when pretty printing ternary expressions.
### Changed
* Made some phpdoc types more precise.
Version 5.0.2 (2024-03-05)
--------------------------
### Fixed
* Fix handling of indentation on next line after opening PHP tag in formatting-preserving pretty
printer.
### Changed
* Avoid cyclic references in `Parser` objects. This means that no longer used parser objects are
immediately destroyed now, instead of requiring cycle GC.
* Update `PhpVersion::getNewestSupported()` to report PHP 8.3 instead of PHP 8.2.
Version 5.0.1 (2024-02-21)
--------------------------
### Changed
* Added check to detect use of PHP-Parser with libraries that define `T_*` compatibility tokens
with incorrect type (such as string instead of int). This would lead to `TypeError`s down the
line. Now an `Error` will be thrown early to indicate the problem.
Version 5.0.0 (2024-01-07)
--------------------------
See UPGRADE-5.0 for detailed migration instructions.
### Fixed
* Fixed parent class of `PropertyItem` and `UseItem`.
Version 5.0.0-rc1 (2023-12-20)
------------------------------
See UPGRADE-5.0 for detailed migration instructions.
### Fixed
* Fixed parsing of empty files.
### Added
* Added support for printing additional attributes (like `kind`) in `NodeDumper`.
* Added `rawValue` attribute to `InterpolatedStringPart` and heredoc/nowdoc `String_`s, which
provides the original, unparsed value. It was previously only available for non-interpolated
single/double quoted strings.
* Added `Stmt\Block` to represent `{}` code blocks. Previously, such code blocks were flattened
into the parent statements array. `Stmt\Block` will not be created for structures that are
typically used with code blocks, for example `if ($x) { $y; }` will be represented as previously,
while `if ($x) { { $x; } }` will have an extra `Stmt\Block` wrapper.
### Changed
* Use visitor to assign comments. This fixes the long-standing issue where comments were assigned
to all nodes sharing a starting position. Now only the outer-most node will hold the comments.
* Don't parse unicode escape sequences when targeting PHP < 7.0.
* Improve NodeDumper performance for large dumps.
### Removed
* Removed `Stmt\Throw_` node, use `Expr\Throw_` inside `Stmt\Expression` instead.
* Removed `ParserFactory::create()`.
Version 5.0.0-beta1 (2023-09-17)
--------------------------------

@@ -2,3 +2,31 @@
This project uses PSR-12 with consistent brace placement. This means that the opening brace is
always on the same line, even for class and method declarations.
## Tools
This project uses PHP-CS-Fixer and PHPStan. You can invoke them using `make`:
```shell
make php-cs-fixer
make phpstan
```
## Adding support for new PHP syntax
1. If necessary, add emulation support for new tokens.
* Add a new subclass of `Lexer\TokenEmulator`. Take inspiration from existing classes.
* Add the new class to the array in `Lexer\Emulative`.
* Add tests for the emulation in `Lexer\EmulativeTest`. You'll want to modify
`provideTestReplaceKeywords()` for new reserved keywords and `provideTestLexNewFeatures()` for
other emulations.
2. Add any new node classes that are needed.
3. Add support for the new syntax in `grammar/php.y`. Regenerate the parser by running
`php grammar/rebuildParsers.php`. Use `--debug` if there are conflicts.
4. Add pretty-printing support by implementing a `pFooBar()` method in `PrettyPrinter\Standard`.
5. Add tests both in `test/code/parser` and `test/code/prettyPrinter`.
6. Add support for formatting-preserving pretty-printing. This is done by modifying the data tables
at the end of `PrettyPrinterAbstract`. Add a test in `test/code/formatPreservation`.
7. Does the new syntax feature namespaced names? If so, add support for name resolution in
`NodeVisitor\NameResolver`. Test it in `NodeVisitor\NameResolverTest`.
8. Does the new syntax require any changes to builders? Is so, make them :)

@@ -4,7 +4,10 @@ tools/vendor:
composer install -d tools
phpstan: tools/vendor
tools/vendor/bin/phpstan
php tools/vendor/bin/phpstan
php-cs-fixer: tools/vendor
tools/vendor/bin/php-cs-fixer fix
php tools/vendor/bin/php-cs-fixer fix
tests:
php vendor/bin/phpunit

@@ -6,11 +6,9 @@ PHP Parser
This is a PHP parser written in PHP. Its purpose is to simplify static code analysis and
manipulation.
[Documentation for version 5.x][doc_master] (in development; for running on PHP >= 7.4; for parsing PHP 7.0 to PHP 8.3, with limited support for parsing PHP 5.x).
[**Documentation for version 5.x**][doc_master] (current; for running on PHP >= 7.4; for parsing PHP 7.0 to PHP 8.4, with limited support for parsing PHP 5.x).
[**Documentation for version 4.x**][doc_4_x] (stable; for running on PHP >= 7.0; for parsing PHP 5.2 to PHP 8.3).
[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 4.x][doc_4_x] (supported; for running on PHP >= 7.0; for parsing PHP 5.2 to PHP 8.3).
Features
--------
@@ -213,9 +211,8 @@ Component documentation:
* [AST builders](doc/component/AST_builders.markdown)
* Fluent builders for AST nodes
* [Lexer](doc/component/Lexer.markdown)
* Lexer options
* Token and file positions for nodes
* Custom attributes
* Emulation
* Tokens, positions and attributes
* [Error handling](doc/component/Error_handling.markdown)
* Column information for errors
* Error recovery (parsing of syntactically incorrect code)

@@ -13,6 +13,7 @@ In particular, if an older `PhpVersion` is specified, then:
* For versions before PHP 7.0, `$foo =& new Bar()` assignments are allowed without error.
* For versions before PHP 7.0, invalid octal literals `089` are allowed without error.
* For versions before PHP 7.0, unicode escape sequences `\u{123}` in strings are not parsed.
* Type hints are interpreted as a class `Name` or as a built-in `Identifier` depending on PHP
version, for example `int` is treated as a class name on PHP 5.6 and as a built-in on PHP 7.0.
@@ -27,16 +28,17 @@ The following symbols are affected by this removal:
* The `PhpParser\Parser\Php5` class has been removed.
* The `PhpParser\Parser\Multiple` class has been removed. While not strictly related to PHP 5 support, this functionality is no longer useful without it.
* The `PhpParser\ParserFactory::ONLY_PHP5` and `PREFER_PHP5` options have been removed.
* The `PhpParser\ParserFactory::PREFER_PHP7` option is now equivalent to `ONLY_PHP7`.
### Changes to the parser factory
The `ParserFactory::create()` method is deprecated in favor of three new methods that provide more fine-grained control over the PHP version being targeted:
The `ParserFactory::create()` method has been removed in favor of three new methods that provide more fine-grained control over the PHP version being targeted:
* `createForNewestSupportedVersion()`: Use this if you don't know the PHP version of the code you're parsing. It's better to assume a too new version than a too old one.
* `createForHostVersion()`: Use this if you're parsing code for the PHP version you're running on.
* `createForVersion()`: Use this if you know the PHP version of the code you want to parse.
The `createForNewestSupportedVersion()` and `createForHostVersion()` are available since PHP-Parser 4.18.0, to allow libraries to support PHP-Parser 4 and 5 at the same time more easily.
In all cases, the PHP version is a fairly weak hint that is only used on a best-effort basis. The parser will usually accept code for newer versions if it does not have any backwards-compatibility implications.
For example, if you specify version `"8.0"`, then `class ReadOnly {}` is treated as a valid class declaration, while using `public readonly int $prop` will lead to a parse error. However, `final public const X = Y;` will be accepted in both cases.
@@ -61,6 +63,35 @@ $parser = $factory->create(ParserFactory::ONLY_PHP5);
$parser = $factory->createForVersion(PhpVersion::fromString("5.6"));
```
### Changes to the throw representation
Previously, `throw` statements like `throw $e;` were represented using the `Stmt\Throw_` class,
while uses inside other expressions (such as `$x ?? throw $e`) used the `Expr\Throw_` class.
Now, `throw $e;` is represented as a `Stmt\Expression` that contains an `Expr\Throw_`. The
`Stmt\Throw_` class has been removed.
```php
# Code
throw $e;
# Before
Stmt_Throw(
expr: Expr_Variable(
name: e
)
)
# After
Stmt_Expression(
expr: Expr_Throw(
expr: Expr_Variable(
name: e
)
)
)
```
### Changes to the array destructuring representation
Previously, the `list($x) = $y` destructuring syntax was represented using a `Node\Expr\List_`
@@ -71,6 +102,49 @@ Now, destructuring is always represented using `Node\Expr\List_`. The `kind` att
`Node\Expr\List_::KIND_LIST` or `Node\Expr\List_::KIND_ARRAY` specifies which syntax was actually
used.
```php
# Code
[$x] = $y;
# Before
Expr_Assign(
var: Expr_Array(
items: array(
0: Expr_ArrayItem(
key: null
value: Expr_Variable(
name: x
)
byRef: false
unpack: false
)
)
)
expr: Expr_Variable(
name: y
)
)
# After
Expr_Assign(
var: Expr_List(
items: array(
0: ArrayItem(
key: null
value: Expr_Variable(
name: x
)
byRef: false
unpack: false
)
)
)
expr: Expr_Variable(
name: y
)
)
```
### Changes to the name representation
Previously, `Name` nodes had a `parts` subnode, which stores an array of name parts, split by
@@ -82,6 +156,123 @@ now represented by `Name(name: 'Foo\Bar')` instead.
It is possible to convert the name to the previous representation using `$name->getParts()`. The
`Name` constructor continues to accept both the string and the array representation.
The `Name::getParts()` method is available since PHP-Parser 4.16.0, to allow libraries to support
PHP-Parser 4 and 5 at the same time more easily.
### Changes to the block representation
Previously, code blocks `{ ... }` were always flattened into their parent statement list. For
example `while ($x) { $a; { $b; } $c; }` would produce the same node structure as
`if ($x) { $a; $b; $c; }`, namely a `Stmt\While_` node whose `stmts` subnode is an array of three
statements.
Now, the nested `{ $b; }` block is represented using an explicit `Stmt\Block` node. However, the
outer `{ $a; { $b; } $c; }` block is still represented using a simple array in the `stmts` subnode.
```php
# Code
while ($x) { $a; { $b; } $c; }
# Before
Stmt_While(
cond: Expr_Variable(
name: x
)
stmts: array(
0: Stmt_Expression(
expr: Expr_Variable(
name: a
)
)
1: Stmt_Expression(
expr: Expr_Variable(
name: b
)
)
2: Stmt_Expression(
expr: Expr_Variable(
name: c
)
)
)
)
# After
Stmt_While(
cond: Expr_Variable(
name: x
)
stmts: array(
0: Stmt_Expression(
expr: Expr_Variable(
name: a
)
)
1: Stmt_Block(
stmts: array(
0: Stmt_Expression(
expr: Expr_Variable(
name: b
)
)
)
)
2: Stmt_Expression(
expr: Expr_Variable(
name: c
)
)
)
)
```
### Changes to comment assignment
Previously, comments were assigned to all nodes starting at the same position. Now they will be
assigned to the outermost node only.
```php
# Code
// Comment
$a + $b;
# Before
Stmt_Expression(
expr: Expr_BinaryOp_Plus(
left: Expr_Variable(
name: a
comments: array(
0: // Comment
)
)
right: Expr_Variable(
name: b
)
comments: array(
0: // Comment
)
)
comments: array(
0: // Comment
)
)
# After
Stmt_Expression(
expr: Expr_BinaryOp_Plus(
left: Expr_Variable(
name: a
)
right: Expr_Variable(
name: b
)
)
comments: array(
0: // Comment
)
)
```
### Renamed nodes
A number of AST nodes have been renamed or moved in the AST hierarchy:
@@ -104,7 +295,7 @@ The old class names have been retained as aliases for backwards compatibility. H
Modifier flags (as used by the `$flags` subnode of `Class_`, `ClassMethod`, `Property`, etc.) are now available as class constants on a separate `PhpParser\Modifiers` class, instead of being part of `PhpParser\Node\Stmt\Class_`, to make it clearer that these are used by many different nodes. The old constants are deprecated, but are still available.
```
```php
PhpParser\Node\Stmt\Class_::MODIFIER_PUBLIC -> PhpParser\Modifiers::PUBLIC
PhpParser\Node\Stmt\Class_::MODIFIER_PROTECTED -> PhpParser\Modifiers::PROTECTED
PhpParser\Node\Stmt\Class_::MODIFIER_PRIVATE -> PhpParser\Modifiers::PRIVATE
@@ -117,7 +308,7 @@ PhpParser\Node\Stmt\Class_::VISIBILITY_MODIFIER_MASK -> PhpParser\Modifiers::VIS
### Changes to node constructors
Node constructor arguments accepting types now longer accept plain strings. Either an `Identifier` or `Name` (or `ComplexType`) should be passed instead. This affects the following constructor arguments:
Node constructor arguments accepting types no longer accept plain strings. Either an `Identifier` or `Name` (or `ComplexType`) should be passed instead. This affects the following constructor arguments:
* The `'returnType'` key of `$subNodes` argument of `Node\Expr\ArrowFunction`.
* The `'returnType'` key of `$subNodes` argument of `Node\Expr\Closure`.
@@ -202,7 +393,7 @@ The pretty printer now accepts a `phpVersion` option, which accepts a `PhpVersio
* For PHP >= 7.0 (default), short array syntax `[]` will be used by default. This does not affect nodes that specify an explicit array syntax using the `kind` attribute.
* For PHP >= 7.0 (default), parentheses around `yield` expressions will only be printed when necessary. Previously, parentheses were always printed, even if `yield` was used as a statement.
* For PHP >= 7.1 (default), the short array syntax `[]` will be used for destructuring by default (instead of `list()`). This does not affect nodes that specify and explicit syntax using the `kind` attribute.
* For PHP >= 7.1 (default), the short array syntax `[]` will be used for destructuring by default (instead of `list()`). This does not affect nodes that specify an explicit syntax using the `kind` attribute.
* For PHP >= 7.3 (default), a newline is no longer forced after heredoc/nowdoc strings, as the requirement for this has been removed with the introduction of flexible heredoc/nowdoc strings.
* For PHP >= 7.3 (default), heredoc/nowdoc strings are now indented just like regular code. This was allowed with the introduction of flexible heredoc/nowdoc strings.
@@ -220,7 +411,7 @@ protected function p(
The `$precedence` is the precedence of the direct parent operator (if any), while `$lhsPrecedence` is that precedence of the nearest binary operator on whose left-hand-side the node occurs. For unary operators, only the `$lhsPrecedence` is relevant.
Recursive calls in pretty-printer methods should generally continue calling `p()` without additional parameters. However, pretty-printer methods for operators that participate in precedence resolution need to be adjusted. For example, typical implementations for operators looks as follows now:
Recursive calls in pretty-printer methods should generally continue calling `p()` without additional parameters. However, pretty-printer methods for operators that participate in precedence resolution need to be adjusted. For example, typical implementations for operators look as follows now:
```php
protected function pExpr_BinaryOp_Plus(
@@ -301,7 +492,7 @@ Additionally, the token array is now terminated by a sentinel token with ID 0.
The lexer API is reduced to a single `Lexer::tokenize()` method, which returns an array of tokens. The `startLexing()` and `getNextToken()` methods have been removed.
Responsibility for determining start and end attributes for nodes has been moved from the lexer to the parser. The lexer no longer accepts an options array. The `usedAttributes` option has been removed without replacement, and the parser will now unconditionally add the `comments`, `startLine`, `endLine`, `startFilePos`, `startEndPos`, `startTokenPos` and `startEndPos` attributes.
Responsibility for determining start and end attributes for nodes has been moved from the lexer to the parser. The lexer no longer accepts an options array. The `usedAttributes` option has been removed without replacement, and the parser will now unconditionally add the `comments`, `startLine`, `endLine`, `startFilePos`, `endFilePos`, `startTokenPos` and `endTokenPos` attributes.
There should no longer be a need to directly interact with the `Lexer` for end users, as the `ParserFactory` will create an appropriate instance, and no additional configuration of the lexer is necessary. To use formatting-preserving pretty printing, the setup boilerplate changes as follows:
@@ -328,7 +519,7 @@ $newStmts = $traverser->traverse($oldStmts);
$parser = (new ParserFactory())->createForNewestSupportedVersion();
$oldStmts = $parser->parse($code);
$oldTokens = $lexer->getTokens();
$oldTokens = $parser->getTokens();
$traverser = new NodeTraverser(new NodeVisitor\CloningVisitor());
$newStmts = $traverser->traverse($oldStmts);

@@ -19,7 +19,7 @@
"ext-ctype": "*"
},
"require-dev": {
"phpunit/phpunit": "^7.0 || ^8.0 || ^9.0",
"phpunit/phpunit": "^9.0",
"ircmaxell/php-yacc": "^0.0.7"
},
"extra": {

@@ -43,7 +43,7 @@ following caveats:
As the parser is based on the tokens returned by `token_get_all` (which is only able to lex the PHP
version it runs on), additionally a wrapper for emulating tokens from newer versions is provided.
This allows to parse PHP 8.3 source code running on PHP 7.4, for example. This emulation is not
This allows to parse PHP 8.4 source code running on PHP 7.4, for example. This emulation is not
perfect, but works well in practice.
Finally, it should be noted that the parser aims to accept all valid code, not reject all invalid

@@ -27,9 +27,8 @@ Component documentation
* [AST builders](component/AST_builders.markdown)
* Fluent builders for AST nodes
* [Lexer](component/Lexer.markdown)
* Lexer options
* Token and file positions for nodes
* Custom attributes
* Emulation
* Tokens, positions and attributes
* [Error handling](component/Error_handling.markdown)
* Column information for errors
* Error recovery (parsing of syntactically incorrect code)

@@ -51,3 +51,17 @@ obtained through `$node->getAttribute('next')`.
`ParentConnectingVisitor` and `NodeConnectingVisitor` should not be used at the same time. The latter
includes the functionality of the former.
How can I limit the impact of cyclic references in the AST?
-----
NodeConnectingVisitor adds a parent reference, which introduces a cycle. This means that the AST can now only be collected by the cycle garbage collector.
This in turn can lead to performance and/or memory issues.
To break the cyclic references between AST nodes `NodeConnectingVisitor` supports a boolean `$weakReferences` constructor parameter.
When set to `true`, all attributes added by `NodeConnectingVisitor` will be wrapped into a `WeakReference` object.
After enabling this parameter, the parent node can be obtained through `$node->getAttribute('weak_parent')`,
the previous node can be obtained through `$node->getAttribute('weak_previous')`, and the next node can be
obtained through `$node->getAttribute('weak_next')`.

@@ -104,7 +104,7 @@ This will result in the following output (which includes attributes):
"nodeType": "Scalar_String",
"attributes": {
"startLine": 5,
"endLine": 5
"endLine": 5,
"kind": 2,
"rawValue": "\"\\n\""
},

@@ -37,6 +37,7 @@ integer should be printed as decimal, hexadecimal, etc). Additionally, it suppor
* `phpVersion` (defaults to 7.4) allows opting into formatting that is not supported by older PHP
versions.
* `newline` (defaults to `"\n"`) can be set to `"\r\n"` in order to produce Windows newlines.
* `indent` (defaults to four spaces `" "`) can be set to any number of spaces or a single tab.
* `shortArraySyntax` determines the used array syntax if the `kind` attribute is not set. This is
a legacy option, and `phpVersion` should be used to control this behavior instead.
@@ -97,5 +98,5 @@ If you make use of the name resolution functionality, you will likely want to di
the AST and causing spurious changes to the pretty printed code. For more information, see the
[name resolution documentation](Name_resolution.markdown).
The formatting-preservation works on a best-effort basis and may sometimes reformat more code tha
The formatting-preservation works on a best-effort basis and may sometimes reformat more code than
necessary. If you encounter problems while using this functionality, please open an issue.

@@ -1,7 +1,7 @@
<?php declare(strict_types=1);
$meta #
#semval($) $this->semValue
#semval($,%t) $this->semValue
#semval($) $self->semValue
#semval($,%t) $self->semValue
#semval(%n) $stackPos-(%l-%n)
#semval(%n,%t) $stackPos-(%l-%n)
@@ -28,68 +28,68 @@ class #(-p) extends \PhpParser\ParserAbstract
public const %s = %n;
#endtokenval
protected $tokenToSymbolMapSize = #(YYMAXLEX);
protected $actionTableSize = #(YYLAST);
protected $gotoTableSize = #(YYGLAST);
protected int $tokenToSymbolMapSize = #(YYMAXLEX);
protected int $actionTableSize = #(YYLAST);
protected int $gotoTableSize = #(YYGLAST);
protected $invalidSymbol = #(YYBADCH);
protected $errorSymbol = #(YYINTERRTOK);
protected $defaultAction = #(YYDEFAULT);
protected $unexpectedTokenRule = #(YYUNEXPECTED);
protected int $invalidSymbol = #(YYBADCH);
protected int $errorSymbol = #(YYINTERRTOK);
protected int $defaultAction = #(YYDEFAULT);
protected int $unexpectedTokenRule = #(YYUNEXPECTED);
protected $YY2TBLSTATE = #(YY2TBLSTATE);
protected $numNonLeafStates = #(YYNLSTATES);
protected int $YY2TBLSTATE = #(YY2TBLSTATE);
protected int $numNonLeafStates = #(YYNLSTATES);
protected $symbolToName = array(
protected array $symbolToName = array(
#listvar terminals
);
protected $tokenToSymbol = array(
protected array $tokenToSymbol = array(
#listvar yytranslate
);
protected $action = array(
protected array $action = array(
#listvar yyaction
);
protected $actionCheck = array(
protected array $actionCheck = array(
#listvar yycheck
);
protected $actionBase = array(
protected array $actionBase = array(
#listvar yybase
);
protected $actionDefault = array(
protected array $actionDefault = array(
#listvar yydefault
);
protected $goto = array(
protected array $goto = array(
#listvar yygoto
);
protected $gotoCheck = array(
protected array $gotoCheck = array(
#listvar yygcheck
);
protected $gotoBase = array(
protected array $gotoBase = array(
#listvar yygbase
);
protected $gotoDefault = array(
protected array $gotoDefault = array(
#listvar yygdefault
);
protected $ruleToNonTerminal = array(
protected array $ruleToNonTerminal = array(
#listvar yylhs
);
protected $ruleToLength = array(
protected array $ruleToLength = array(
#listvar yylen
);
#if -t
protected $productions = array(
protected array $productions = array(
#production-strings;
);
#endif
@@ -97,7 +97,7 @@ class #(-p) extends \PhpParser\ParserAbstract
protected function initReduceCallbacks(): void {
$this->reduceCallbacks = [
#reduce
%n => function ($stackPos) {
%n => static function ($self, $stackPos) {
%b
},
#noact

@@ -1,6 +1,7 @@
%pure_parser
%expect 2
%right T_VOID_CAST
%right T_THROW
%left T_INCLUDE T_INCLUDE_ONCE T_EVAL T_REQUIRE T_REQUIRE_ONCE
%left ','
@@ -26,6 +27,7 @@
%left '+' '-' '.'
#endif
#if PHP8
%left T_PIPE
%left '.'
%left T_SL T_SR
%left '+' '-'
@@ -81,7 +83,10 @@
%token T_USE
%token T_INSTEADOF
%token T_GLOBAL
%right T_STATIC T_ABSTRACT T_FINAL T_PRIVATE T_PROTECTED T_PUBLIC T_READONLY
%token T_STATIC T_ABSTRACT T_FINAL T_PRIVATE T_PROTECTED T_PUBLIC T_READONLY
%token T_PUBLIC_SET
%token T_PROTECTED_SET
%token T_PRIVATE_SET
%token T_VAR
%token T_UNSET
%token T_ISSET
@@ -103,6 +108,7 @@
%token T_TRAIT_C
%token T_METHOD_C
%token T_FUNC_C
%token T_PROPERTY_C
%token T_LINE
%token T_FILE
%token T_START_HEREDOC
@@ -252,8 +258,11 @@ top_statement:
$this->checkNamespace($$); }
| T_USE use_declarations semi { $$ = Stmt\Use_[$2, Stmt\Use_::TYPE_NORMAL]; }
| T_USE use_type use_declarations semi { $$ = Stmt\Use_[$3, $2]; }
| group_use_declaration semi
| T_CONST constant_declaration_list semi { $$ = Stmt\Const_[$2]; }
| group_use_declaration
| T_CONST constant_declaration_list semi { $$ = new Stmt\Const_($2, attributes(), []); }
| attributes T_CONST constant_declaration_list semi
{ $$ = new Stmt\Const_($3, attributes(), $1);
$this->checkConstantAttributes($$); }
;
use_type:
@@ -262,9 +271,9 @@ use_type:
;
group_use_declaration:
T_USE use_type legacy_namespace_name T_NS_SEPARATOR '{' unprefixed_use_declarations '}'
T_USE use_type legacy_namespace_name T_NS_SEPARATOR '{' unprefixed_use_declarations '}' semi
{ $$ = Stmt\GroupUse[$3, $6, $2]; }
| T_USE legacy_namespace_name T_NS_SEPARATOR '{' inline_use_declarations '}'
| T_USE legacy_namespace_name T_NS_SEPARATOR '{' inline_use_declarations '}' semi
{ $$ = Stmt\GroupUse[$2, $5, Stmt\Use_::TYPE_UNKNOWN]; }
;
@@ -366,21 +375,13 @@ inner_statement:
;
non_empty_statement:
'{' inner_statement_list '}'
{
if ($2) {
$$ = $2; prependLeadingComments($$);
} else {
makeNop($$);
if (null === $$) { $$ = array(); }
}
}
| T_IF '(' expr ')' statement elseif_list else_single
{ $$ = Stmt\If_[$3, ['stmts' => toArray($5), 'elseifs' => $6, 'else' => $7]]; }
'{' inner_statement_list '}' { $$ = Stmt\Block[$2]; }
| T_IF '(' expr ')' blocklike_statement elseif_list else_single
{ $$ = Stmt\If_[$3, ['stmts' => $5, 'elseifs' => $6, 'else' => $7]]; }
| T_IF '(' expr ')' ':' inner_statement_list new_elseif_list new_else_single T_ENDIF ';'
{ $$ = Stmt\If_[$3, ['stmts' => $6, 'elseifs' => $7, 'else' => $8]]; }
| T_WHILE '(' expr ')' while_statement { $$ = Stmt\While_[$3, $5]; }
| T_DO statement T_WHILE '(' expr ')' ';' { $$ = Stmt\Do_ [$5, toArray($2)]; }
| T_DO blocklike_statement T_WHILE '(' expr ')' ';' { $$ = Stmt\Do_ [$5, $2]; }
| T_FOR '(' for_expr ';' for_expr ';' for_expr ')' for_statement
{ $$ = Stmt\For_[['init' => $3, 'cond' => $5, 'loop' => $7, 'stmts' => $9]]; }
| T_SWITCH '(' expr ')' switch_case_list { $$ = Stmt\Switch_[$3, $5]; }
@@ -394,16 +395,7 @@ non_empty_statement:
$$ = Stmt\InlineHTML[$1];
$$->setAttribute('hasLeadingNewline', $this->inlineHtmlHasLeadingNewline(#1));
}
| expr semi {
$e = $1;
if ($e instanceof Expr\Throw_) {
// For backwards-compatibility reasons, convert throw in statement position into
// Stmt\Throw_ rather than Stmt\Expression(Expr\Throw_).
$$ = Stmt\Throw_[$e->expr];
} else {
$$ = Stmt\Expression[$e];
}
}
| expr semi { $$ = Stmt\Expression[$1]; }
| T_UNSET '(' variables_list ')' semi { $$ = Stmt\Unset_[$3]; }
| T_FOREACH '(' expr T_AS foreach_variable ')' foreach_statement
{ $$ = Stmt\Foreach_[$3, $5[0], ['keyVar' => null, 'byRef' => $5[1], 'stmts' => $7]]; }
@@ -416,14 +408,16 @@ non_empty_statement:
{ $$ = Stmt\TryCatch[$3, $5, $6]; $this->checkTryCatch($$); }
| T_GOTO identifier_not_reserved semi { $$ = Stmt\Goto_[$2]; }
| identifier_not_reserved ':' { $$ = Stmt\Label[$1]; }
| error { $$ = array(); /* means: no statement */ }
| error { $$ = null; /* means: no statement */ }
;
statement:
non_empty_statement
| ';'
{ makeNop($$);
if ($$ === null) $$ = array(); /* means: no statement */ }
| ';' { makeNop($$); }
;
blocklike_statement:
statement { toBlock($1); }
;
catches:
@@ -475,15 +469,17 @@ block_or_error:
| error { $$ = []; }
;
identifier_maybe_readonly:
fn_identifier:
identifier_not_reserved
| T_READONLY { $$ = Node\Identifier[$1]; }
| T_EXIT { $$ = Node\Identifier[$1]; }
| T_CLONE { $$ = Node\Identifier[$1]; }
;
function_declaration_statement:
T_FUNCTION optional_ref identifier_maybe_readonly '(' parameter_list ')' optional_return_type block_or_error
T_FUNCTION optional_ref fn_identifier '(' 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_maybe_readonly '(' parameter_list ')' optional_return_type block_or_error
| attributes T_FUNCTION optional_ref fn_identifier '(' parameter_list ')' optional_return_type block_or_error
{ $$ = Stmt\Function_[$4, ['byRef' => $3, 'params' => $6, 'returnType' => $8, 'stmts' => $9, 'attrGroups' => $1]]; }
;
@@ -554,17 +550,17 @@ non_empty_class_name_list:
;
for_statement:
statement { $$ = toArray($1); }
blocklike_statement
| ':' inner_statement_list T_ENDFOR ';' { $$ = $2; }
;
foreach_statement:
statement { $$ = toArray($1); }
blocklike_statement
| ':' inner_statement_list T_ENDFOREACH ';' { $$ = $2; }
;
declare_statement:
non_empty_statement { $$ = toArray($1); }
non_empty_statement { toBlock($1); }
| ';' { $$ = null; }
| ':' inner_statement_list T_ENDDECLARE ';' { $$ = $2; }
;
@@ -624,7 +620,7 @@ match_arm:
;
while_statement:
statement { $$ = toArray($1); }
blocklike_statement { $$ = $1; }
| ':' inner_statement_list T_ENDWHILE ';' { $$ = $2; }
;
@@ -634,7 +630,7 @@ elseif_list:
;
elseif:
T_ELSEIF '(' expr ')' statement { $$ = Stmt\ElseIf_[$3, toArray($5)]; }
T_ELSEIF '(' expr ')' blocklike_statement { $$ = Stmt\ElseIf_[$3, $5]; }
;
new_elseif_list:
@@ -649,7 +645,7 @@ new_elseif:
else_single:
/* empty */ { $$ = null; }
| T_ELSE statement { $$ = Stmt\Else_[toArray($2)]; }
| T_ELSE blocklike_statement { $$ = Stmt\Else_[$2]; }
;
new_else_single:
@@ -686,18 +682,24 @@ property_modifier:
T_PUBLIC { $$ = Modifiers::PUBLIC; }
| T_PROTECTED { $$ = Modifiers::PROTECTED; }
| T_PRIVATE { $$ = Modifiers::PRIVATE; }
| T_PUBLIC_SET { $$ = Modifiers::PUBLIC_SET; }
| T_PROTECTED_SET { $$ = Modifiers::PROTECTED_SET; }
| T_PRIVATE_SET { $$ = Modifiers::PRIVATE_SET; }
| T_READONLY { $$ = Modifiers::READONLY; }
| T_FINAL { $$ = Modifiers::FINAL; }
;
parameter:
optional_attributes optional_property_modifiers optional_type_without_static
optional_arg_ref optional_ellipsis plain_variable
{ $$ = new Node\Param($6, null, $3, $4, $5, attributes(), $2, $1);
$this->checkParam($$); }
optional_arg_ref optional_ellipsis plain_variable optional_property_hook_list
{ $$ = new Node\Param($6, null, $3, $4, $5, attributes(), $2, $1, $7);
$this->checkParam($$);
$this->addPropertyNameToHooks($$); }
| optional_attributes optional_property_modifiers optional_type_without_static
optional_arg_ref optional_ellipsis plain_variable '=' expr
{ $$ = new Node\Param($6, $8, $3, $4, $5, attributes(), $2, $1);
$this->checkParam($$); }
optional_arg_ref optional_ellipsis plain_variable '=' expr optional_property_hook_list
{ $$ = new Node\Param($6, $8, $3, $4, $5, attributes(), $2, $1, $9);
$this->checkParam($$);
$this->addPropertyNameToHooks($$); }
| optional_attributes optional_property_modifiers optional_type_without_static
optional_arg_ref optional_ellipsis error
{ $$ = new Node\Param(Expr\Error[], null, $3, $4, $5, attributes(), $2, $1); }
@@ -786,6 +788,22 @@ argument_list:
| '(' variadic_placeholder ')' { init($2); }
;
clone_argument_list:
'(' ')' { $$ = array(); }
| '(' non_empty_clone_argument_list optional_comma ')' { $$ = $2; }
| '(' expr ',' ')' { init(Node\Arg[$2, false, false]); }
| '(' variadic_placeholder ')' { init($2); }
;
non_empty_clone_argument_list:
expr ',' argument
{ init(new Node\Arg($1, false, false, stackAttributes(#1)), $3); }
| argument_no_expr
{ init($1); }
| non_empty_clone_argument_list ',' argument
{ push($1, $3); }
;
variadic_placeholder:
T_ELLIPSIS { $$ = Node\VariadicPlaceholder[]; }
;
@@ -795,14 +813,18 @@ non_empty_argument_list:
| non_empty_argument_list ',' argument { push($1, $3); }
;
argument:
expr { $$ = Node\Arg[$1, false, false]; }
| ampersand variable { $$ = Node\Arg[$2, true, false]; }
argument_no_expr:
ampersand variable { $$ = Node\Arg[$2, true, false]; }
| T_ELLIPSIS expr { $$ = Node\Arg[$2, false, true]; }
| identifier_maybe_reserved ':' expr
{ $$ = new Node\Arg($3, false, false, attributes(), $1); }
;
argument:
expr { $$ = Node\Arg[$1, false, false]; }
| argument_no_expr { $$ = $1; }
;
global_var_list:
non_empty_global_var_list no_comma
;
@@ -831,7 +853,7 @@ static_var:
;
class_statement_list_ex:
class_statement_list_ex class_statement { if ($2 !== null) { push($1, $2); } }
class_statement_list_ex class_statement { if ($2 !== null) { push($1, $2); } else { $$ = $1; } }
| /* empty */ { init(); }
;
@@ -843,8 +865,14 @@ class_statement_list:
class_statement:
optional_attributes variable_modifiers optional_type_without_static property_declaration_list semi
{ $$ = new Stmt\Property($2, $4, attributes(), $3, $1);
$this->checkProperty($$, #2); }
{ $$ = new Stmt\Property($2, $4, attributes(), $3, $1); }
#if PHP8
| optional_attributes variable_modifiers optional_type_without_static property_declaration_list '{' property_hook_list '}'
{ $$ = new Stmt\Property($2, $4, attributes(), $3, $1, $6);
$this->checkPropertyHooksForMultiProperty($$, #5);
$this->checkEmptyPropertyHookList($6, #5);
$this->addPropertyNameToHooks($$); }
#endif
| optional_attributes method_modifiers T_CONST class_const_list semi
{ $$ = new Stmt\ClassConst($4, $2, attributes(), $1);
$this->checkClassConst($$, #2); }
@@ -916,6 +944,9 @@ member_modifier:
T_PUBLIC { $$ = Modifiers::PUBLIC; }
| T_PROTECTED { $$ = Modifiers::PROTECTED; }
| T_PRIVATE { $$ = Modifiers::PRIVATE; }
| T_PUBLIC_SET { $$ = Modifiers::PUBLIC_SET; }
| T_PROTECTED_SET { $$ = Modifiers::PROTECTED_SET; }
| T_PRIVATE_SET { $$ = Modifiers::PRIVATE_SET; }
| T_STATIC { $$ = Modifiers::STATIC; }
| T_ABSTRACT { $$ = Modifiers::ABSTRACT; }
| T_FINAL { $$ = Modifiers::FINAL; }
@@ -941,6 +972,39 @@ property_declaration:
| property_decl_name '=' expr { $$ = Node\PropertyItem[$1, $3]; }
;
property_hook_list:
/* empty */ { $$ = []; }
| property_hook_list property_hook { push($1, $2); }
;
optional_property_hook_list:
/* empty */ { $$ = []; }
#if PHP8
| '{' property_hook_list '}' { $$ = $2; $this->checkEmptyPropertyHookList($2, #1); }
#endif
;
property_hook:
optional_attributes property_hook_modifiers optional_ref identifier_not_reserved property_hook_body
{ $$ = Node\PropertyHook[$4, $5, ['flags' => $2, 'byRef' => $3, 'params' => [], 'attrGroups' => $1]];
$this->checkPropertyHook($$, null); }
| optional_attributes property_hook_modifiers optional_ref identifier_not_reserved '(' parameter_list ')' property_hook_body
{ $$ = Node\PropertyHook[$4, $8, ['flags' => $2, 'byRef' => $3, 'params' => $6, 'attrGroups' => $1]];
$this->checkPropertyHook($$, #5); }
;
property_hook_body:
';' { $$ = null; }
| '{' inner_statement_list '}' { $$ = $2; }
| T_DOUBLE_ARROW expr ';' { $$ = $2; }
;
property_hook_modifiers:
/* empty */ { $$ = 0; }
| property_hook_modifiers member_modifier
{ $this->checkPropertyHookModifiers($1, $2, #2); $$ = $1 | $2; }
;
expr_list_forbid_comma:
non_empty_expr_list no_comma
;
@@ -974,6 +1038,7 @@ expr:
}
| new_expr
| match
| T_CLONE clone_argument_list { $$ = Expr\FuncCall[new Node\Name($1, stackAttributes(#1)), $2]; }
| T_CLONE expr { $$ = Expr\Clone_[$2]; }
| variable T_PLUS_EQUAL expr { $$ = Expr\AssignOp\Plus [$1, $3]; }
| variable T_MINUS_EQUAL expr { $$ = Expr\AssignOp\Minus [$1, $3]; }
@@ -1023,6 +1088,9 @@ expr:
| expr T_IS_SMALLER_OR_EQUAL expr { $$ = Expr\BinaryOp\SmallerOrEqual[$1, $3]; }
| expr '>' expr { $$ = Expr\BinaryOp\Greater [$1, $3]; }
| expr T_IS_GREATER_OR_EQUAL expr { $$ = Expr\BinaryOp\GreaterOrEqual[$1, $3]; }
#if PHP8
| expr T_PIPE expr { $$ = Expr\BinaryOp\Pipe[$1, $3]; }
#endif
| expr T_INSTANCEOF class_name_reference { $$ = Expr\Instanceof_[$1, $3]; }
| '(' expr ')' { $$ = $2; }
| expr '?' expr ':' expr { $$ = Expr\Ternary[$1, $3, $5]; }
@@ -1045,10 +1113,9 @@ expr:
| T_OBJECT_CAST expr { $$ = Expr\Cast\Object_ [$2]; }
| T_BOOL_CAST expr { $$ = Expr\Cast\Bool_ [$2]; }
| T_UNSET_CAST expr { $$ = Expr\Cast\Unset_ [$2]; }
| T_EXIT exit_expr
{ $attrs = attributes();
$attrs['kind'] = strtolower($1) === 'exit' ? Expr\Exit_::KIND_EXIT : Expr\Exit_::KIND_DIE;
$$ = new Expr\Exit_($2, $attrs); }
| T_VOID_CAST expr { $$ = Expr\Cast\Void_ [$2]; }
| T_EXIT ctor_arguments
{ $$ = $this->createExitExpr($1, #1, $2, attributes()); }
| '@' expr { $$ = Expr\ErrorSuppress[$2]; }
| scalar
| '`' backticks_expr '`' { $$ = Expr\ShellExec[$2]; }
@@ -1084,12 +1151,21 @@ anonymous_class:
$this->checkClass($$[0], -1); }
;
new_expr:
T_NEW class_name_reference ctor_arguments { $$ = Expr\New_[$2, $3]; }
new_dereferenceable:
T_NEW class_name_reference argument_list { $$ = Expr\New_[$2, $3]; }
| T_NEW anonymous_class
{ list($class, $ctorArgs) = $2; $$ = Expr\New_[$class, $ctorArgs]; }
;
new_non_dereferenceable:
T_NEW class_name_reference { $$ = Expr\New_[$2, []]; }
;
new_expr:
new_dereferenceable
| new_non_dereferenceable
;
lexical_vars:
/* empty */ { $$ = array(); }
| T_USE '(' lexical_var_list ')' { $$ = $3; }
@@ -1141,19 +1217,14 @@ class_name_reference:
class_name_or_var:
class_name
| fully_dereferencable
;
exit_expr:
/* empty */ { $$ = null; }
| '(' optional_expr ')' { $$ = $2; }
| fully_dereferenceable
;
backticks_expr:
/* empty */ { $$ = array(); }
| T_ENCAPSED_AND_WHITESPACE
{ $$ = array(Node\InterpolatedStringPart[Scalar\String_::parseEscapeSequences($1, '`')]); }
| encaps_list { parseEncapsed($1, '`', true); $$ = $1; }
| encaps_string_part
{ $$ = array($1); parseEncapsed($$, '`', $this->phpVersion->supportsUnicodeEscapes()); }
| encaps_list { parseEncapsed($1, '`', $this->phpVersion->supportsUnicodeEscapes()); $$ = $1; }
;
ctor_arguments:
@@ -1171,6 +1242,7 @@ constant:
| T_METHOD_C { $$ = Scalar\MagicConst\Method[]; }
| T_FUNC_C { $$ = Scalar\MagicConst\Function_[]; }
| T_NS_C { $$ = Scalar\MagicConst\Namespace_[]; }
| T_PROPERTY_C { $$ = Scalar\MagicConst\Property[]; }
;
class_constant:
@@ -1190,23 +1262,24 @@ array_short_syntax:
$$ = new Expr\Array_($2, $attrs); }
;
dereferencable_scalar:
dereferenceable_scalar:
T_ARRAY '(' array_pair_list ')'
{ $attrs = attributes(); $attrs['kind'] = Expr\Array_::KIND_LONG;
$$ = new Expr\Array_($3, $attrs);
$this->createdArrays->attach($$); }
| array_short_syntax { $$ = $1; $this->createdArrays->attach($$); }
| T_CONSTANT_ENCAPSED_STRING { $$ = Scalar\String_::fromString($1, attributes()); }
| T_CONSTANT_ENCAPSED_STRING
{ $$ = Scalar\String_::fromString($1, attributes(), $this->phpVersion->supportsUnicodeEscapes()); }
| '"' encaps_list '"'
{ $attrs = attributes(); $attrs['kind'] = Scalar\String_::KIND_DOUBLE_QUOTED;
parseEncapsed($2, '"', true); $$ = new Scalar\InterpolatedString($2, $attrs); }
parseEncapsed($2, '"', $this->phpVersion->supportsUnicodeEscapes()); $$ = new Scalar\InterpolatedString($2, $attrs); }
;
scalar:
T_LNUMBER
{ $$ = $this->parseLNumber($1, attributes(), $this->phpVersion->allowsInvalidOctals()); }
| T_DNUMBER { $$ = Scalar\Float_::fromString($1, attributes()); }
| dereferencable_scalar
| dereferenceable_scalar
| constant
| class_constant
| T_START_HEREDOC T_ENCAPSED_AND_WHITESPACE T_END_HEREDOC
@@ -1222,32 +1295,36 @@ optional_expr:
| expr
;
fully_dereferencable:
fully_dereferenceable:
variable
| '(' expr ')' { $$ = $2; }
| dereferencable_scalar
| dereferenceable_scalar
| class_constant
| new_dereferenceable
;
array_object_dereferencable:
fully_dereferencable
array_object_dereferenceable:
fully_dereferenceable
| constant
;
callable_expr:
callable_variable
| '(' expr ')' { $$ = $2; }
| dereferencable_scalar
| dereferenceable_scalar
| new_dereferenceable
;
callable_variable:
simple_variable
| array_object_dereferencable '[' optional_expr ']' { $$ = Expr\ArrayDimFetch[$1, $3]; }
| array_object_dereferencable '{' expr '}' { $$ = Expr\ArrayDimFetch[$1, $3]; }
| array_object_dereferenceable '[' optional_expr ']' { $$ = Expr\ArrayDimFetch[$1, $3]; }
#if PHP7
| array_object_dereferenceable '{' expr '}' { $$ = Expr\ArrayDimFetch[$1, $3]; }
#endif
| function_call
| array_object_dereferencable T_OBJECT_OPERATOR property_name argument_list
| array_object_dereferenceable T_OBJECT_OPERATOR property_name argument_list
{ $$ = Expr\MethodCall[$1, $3, $4]; }
| array_object_dereferencable T_NULLSAFE_OBJECT_OPERATOR property_name argument_list
| array_object_dereferenceable T_NULLSAFE_OBJECT_OPERATOR property_name argument_list
{ $$ = Expr\NullsafeMethodCall[$1, $3, $4]; }
;
@@ -1259,9 +1336,9 @@ optional_plain_variable:
variable:
callable_variable
| static_member
| array_object_dereferencable T_OBJECT_OPERATOR property_name
| array_object_dereferenceable T_OBJECT_OPERATOR property_name
{ $$ = Expr\PropertyFetch[$1, $3]; }
| array_object_dereferencable T_NULLSAFE_OBJECT_OPERATOR property_name
| array_object_dereferenceable T_NULLSAFE_OBJECT_OPERATOR property_name
{ $$ = Expr\NullsafePropertyFetch[$1, $3]; }
;
@@ -1285,7 +1362,9 @@ static_member:
new_variable:
simple_variable
| new_variable '[' optional_expr ']' { $$ = Expr\ArrayDimFetch[$1, $3]; }
#if PHP7
| new_variable '{' expr '}' { $$ = Expr\ArrayDimFetch[$1, $3]; }
#endif
| new_variable T_OBJECT_OPERATOR property_name { $$ = Expr\PropertyFetch[$1, $3]; }
| new_variable T_NULLSAFE_OBJECT_OPERATOR property_name { $$ = Expr\NullsafePropertyFetch[$1, $3]; }
| class_name T_PAAMAYIM_NEKUDOTAYIM static_member_prop_name
@@ -1352,7 +1431,8 @@ encaps_list:
;
encaps_string_part:
T_ENCAPSED_AND_WHITESPACE { $$ = Node\InterpolatedStringPart[$1]; }
T_ENCAPSED_AND_WHITESPACE
{ $attrs = attributes(); $attrs['rawValue'] = $1; $$ = new Node\InterpolatedStringPart($1, $attrs); }
;
encaps_str_varname:

@@ -23,6 +23,7 @@ function preprocessGrammar($code) {
$code = resolveNodes($code);
$code = resolveMacros($code);
$code = resolveStackAccess($code);
$code = str_replace('$this', '$self', $code);
return $code;
}
@@ -87,14 +88,15 @@ function resolveMacros($code) {
if ('pushNormalizing' === $name) {
assertArgs(2, $args, $name);
return 'if (is_array(' . $args[1] . ')) { $$ = array_merge(' . $args[0] . ', ' . $args[1] . '); }'
. ' else { ' . $args[0] . '[] = ' . $args[1] . '; $$ = ' . $args[0] . '; }';
return 'if (' . $args[1] . ' !== null) { ' . $args[0] . '[] = ' . $args[1] . '; } $$ = ' . $args[0] . ';';
}
if ('toArray' == $name) {
if ('toBlock' == $name) {
assertArgs(1, $args, $name);
return 'is_array(' . $args[0] . ') ? ' . $args[0] . ' : array(' . $args[0] . ')';
return 'if (' . $args[0] . ' instanceof Stmt\Block) { $$ = ' . $args[0] . '->stmts; } '
. 'else if (' . $args[0] . ' === null) { $$ = []; } '
. 'else { $$ = [' . $args[0] . ']; }';
}
if ('parseVar' === $name) {
@@ -122,15 +124,6 @@ function resolveMacros($code) {
return $args[0] . ' = $this->maybeCreateZeroLengthNop($this->tokenPos);';
}
if ('prependLeadingComments' === $name) {
assertArgs(1, $args, $name);
return '$comments = $this->getCommentsBeforeToken($this->tokenStartStack[#1]); $stmts = ' . $args[0] . '; '
. 'if (!empty($comments)) {'
. '$stmts[0]->setAttribute(\'comments\', '
. 'array_merge($comments, $stmts[0]->getAttribute(\'comments\', []))); }';
}
return $matches[0];
},
$code

@@ -28,7 +28,7 @@ class ClassConst implements PhpParser\Builder {
* Creates a class constant builder
*
* @param string|Identifier $name Name
* @param Node\Expr|bool|null|int|float|string|array $value Value
* @param Node\Expr|bool|null|int|float|string|array|\UnitEnum $value Value
*/
public function __construct($name, $value) {
$this->constants = [new Const_($name, BuilderHelpers::normalizeValue($value))];
@@ -38,7 +38,7 @@ class ClassConst implements PhpParser\Builder {
* Add another constant to const group
*
* @param string|Identifier $name Name
* @param Node\Expr|bool|null|int|float|string|array $value Value
* @param Node\Expr|bool|null|int|float|string|array|\UnitEnum $value Value
*
* @return $this The builder instance (for fluid interface)
*/

@@ -13,7 +13,6 @@ use PhpParser\Node\Stmt;
class EnumCase implements PhpParser\Builder {
/** @var Identifier|string */
protected $name;
/** @var ?Node\Expr */
protected ?Node\Expr $value = null;
/** @var array<string, mixed> */
protected array $attributes = [];

@@ -122,6 +122,28 @@ class Param implements PhpParser\Builder {
return $this;
}
/**
* Gives the promoted property private(set) visibility.
*
* @return $this The builder instance (for fluid interface)
*/
public function makePrivateSet() {
$this->flags = BuilderHelpers::addModifier($this->flags, Modifiers::PRIVATE_SET);
return $this;
}
/**
* Gives the promoted property protected(set) visibility.
*
* @return $this The builder instance (for fluid interface)
*/
public function makeProtectedSet() {
$this->flags = BuilderHelpers::addModifier($this->flags, Modifiers::PROTECTED_SET);
return $this;
}
/**
* Adds an attribute group.
*

@@ -23,6 +23,8 @@ class Property implements PhpParser\Builder {
protected ?Node $type = null;
/** @var list<Node\AttributeGroup> */
protected array $attributeGroups = [];
/** @var list<Node\PropertyHook> */
protected array $hooks = [];
/**
* Creates a property builder.
@@ -88,6 +90,50 @@ class Property implements PhpParser\Builder {
return $this;
}
/**
* Makes the property abstract. Requires at least one property hook to be specified as well.
*
* @return $this The builder instance (for fluid interface)
*/
public function makeAbstract() {
$this->flags = BuilderHelpers::addModifier($this->flags, Modifiers::ABSTRACT);
return $this;
}
/**
* Makes the property final.
*
* @return $this The builder instance (for fluid interface)
*/
public function makeFinal() {
$this->flags = BuilderHelpers::addModifier($this->flags, Modifiers::FINAL);
return $this;
}
/**
* Gives the property private(set) visibility.
*
* @return $this The builder instance (for fluid interface)
*/
public function makePrivateSet() {
$this->flags = BuilderHelpers::addModifier($this->flags, Modifiers::PRIVATE_SET);
return $this;
}
/**
* Gives the property protected(set) visibility.
*
* @return $this The builder instance (for fluid interface)
*/
public function makeProtectedSet() {
$this->flags = BuilderHelpers::addModifier($this->flags, Modifiers::PROTECTED_SET);
return $this;
}
/**
* Sets default value for the property.
*
@@ -142,12 +188,27 @@ class Property implements PhpParser\Builder {
return $this;
}
/**
* Adds a property hook.
*
* @return $this The builder instance (for fluid interface)
*/
public function addHook(Node\PropertyHook $hook) {
$this->hooks[] = $hook;
return $this;
}
/**
* Returns the built class node.
*
* @return Stmt\Property The built property node
*/
public function getNode(): PhpParser\Node {
if ($this->flags & Modifiers::ABSTRACT && !$this->hooks) {
throw new PhpParser\Error('Only hooked properties may be declared abstract');
}
return new Stmt\Property(
$this->flags !== 0 ? $this->flags : Modifiers::PUBLIC,
[
@@ -155,7 +216,8 @@ class Property implements PhpParser\Builder {
],
$this->attributes,
$this->type,
$this->attributeGroups
$this->attributeGroups,
$this->hooks
);
}
}

@@ -210,7 +210,7 @@ class BuilderFactory {
/**
* Creates node a for a literal value.
*
* @param Expr|bool|null|int|float|string|array $value $value
* @param Expr|bool|null|int|float|string|array|\UnitEnum $value $value
*/
public function val($value): Expr {
return BuilderHelpers::normalizeValue($value);

@@ -6,6 +6,7 @@ use PhpParser\Node\ComplexType;
use PhpParser\Node\Expr;
use PhpParser\Node\Identifier;
use PhpParser\Node\Name;
use PhpParser\Node\Name\FullyQualified;
use PhpParser\Node\NullableType;
use PhpParser\Node\Scalar;
use PhpParser\Node\Stmt;
@@ -214,7 +215,7 @@ final class BuilderHelpers {
* Normalizes a value: Converts nulls, booleans, integers,
* floats, strings and arrays into their respective nodes
*
* @param Node\Expr|bool|null|int|float|string|array $value The value to normalize
* @param Node\Expr|bool|null|int|float|string|array|\UnitEnum $value The value to normalize
*
* @return Expr The normalized value
*/
@@ -268,6 +269,10 @@ final class BuilderHelpers {
return new Expr\Array_($items);
}
if ($value instanceof \UnitEnum) {
return new Expr\ClassConstFetch(new FullyQualified(\get_class($value)), new Identifier($value->name));
}
throw new \LogicException('Invalid value');
}

@@ -46,6 +46,7 @@ class Comment implements \JsonSerializable {
* Gets the line number the comment started on.
*
* @return int Line number (or -1 if not available)
* @phpstan-return -1|positive-int
*/
public function getStartLine(): int {
return $this->startLine;
@@ -73,6 +74,7 @@ class Comment implements \JsonSerializable {
* Gets the line number the comment ends on.
*
* @return int Line number (or -1 if not available)
* @phpstan-return -1|positive-int
*/
public function getEndLine(): int {
return $this->endLine;

@@ -215,6 +215,9 @@ class ConstExprEvaluator {
case '<': return $this->evaluate($l) < $this->evaluate($r);
case '<=': return $this->evaluate($l) <= $this->evaluate($r);
case '<=>': return $this->evaluate($l) <=> $this->evaluate($r);
case '|>':
$lval = $this->evaluate($l);
return $this->evaluate($r)($lval);
}
throw new \Exception('Should not happen');

@@ -32,6 +32,7 @@ class Error extends \RuntimeException {
* Gets the line the error starts in.
*
* @return int Error start line
* @phpstan-return -1|positive-int
*/
public function getStartLine(): int {
return $this->attributes['startLine'] ?? -1;
@@ -41,6 +42,7 @@ class Error extends \RuntimeException {
* Gets the line the error ends in.
*
* @return int Error end line
* @phpstan-return -1|positive-int
*/
public function getEndLine(): int {
return $this->attributes['endLine'] ?? -1;

@@ -20,9 +20,9 @@ class TokenStream {
*
* @param Token[] $tokens Tokens in PhpToken::tokenize() format
*/
public function __construct(array $tokens) {
public function __construct(array $tokens, int $tabWidth) {
$this->tokens = $tokens;
$this->indentMap = $this->calcIndentMap();
$this->indentMap = $this->calcIndentMap($tabWidth);
}
/**
@@ -195,12 +195,6 @@ class TokenStream {
return false;
}
public function haveBracesInRange(int $startPos, int $endPos): bool {
return $this->haveTokenInRange($startPos, $endPos, '{')
|| $this->haveTokenInRange($startPos, $endPos, T_CURLY_OPEN)
|| $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);
@@ -254,17 +248,21 @@ class TokenStream {
*
* @return int[] Token position to indentation map
*/
private function calcIndentMap(): array {
private function calcIndentMap(int $tabWidth): array {
$indentMap = [];
$indent = 0;
foreach ($this->tokens as $token) {
foreach ($this->tokens as $i => $token) {
$indentMap[] = $indent;
if ($token->id === \T_WHITESPACE) {
$content = $token->text;
$newlinePos = \strrpos($content, "\n");
if (false !== $newlinePos) {
$indent = \strlen($content) - $newlinePos - 1;
$indent = $this->getIndent(\substr($content, $newlinePos + 1), $tabWidth);
} elseif ($i === 1 && $this->tokens[0]->id === \T_OPEN_TAG &&
$this->tokens[0]->text[\strlen($this->tokens[0]->text) - 1] === "\n") {
// Special case: Newline at the end of opening tag followed by whitespace.
$indent = $this->getIndent($content, $tabWidth);
}
}
}
@@ -274,4 +272,11 @@ class TokenStream {
return $indentMap;
}
private function getIndent(string $ws, int $tabWidth): int {
$spaces = \substr_count($ws, " ");
$tabs = \substr_count($ws, "\t");
assert(\strlen($ws) === $spaces + $tabs);
return $spaces + $tabs * $tabWidth;
}
}

@@ -76,7 +76,7 @@ class Lexer {
$numTokens = \count($tokens);
if ($numTokens === 0) {
// Empty input edge case: Just add the sentinel token.
$tokens[] = [new Token(0, "\0", 1, 0)];
$tokens[] = new Token(0, "\0", 1, 0);
return;
}

@@ -5,19 +5,19 @@ namespace PhpParser\Lexer;
use PhpParser\Error;
use PhpParser\ErrorHandler;
use PhpParser\Lexer;
use PhpParser\Lexer\TokenEmulator\AsymmetricVisibilityTokenEmulator;
use PhpParser\Lexer\TokenEmulator\AttributeEmulator;
use PhpParser\Lexer\TokenEmulator\EnumTokenEmulator;
use PhpParser\Lexer\TokenEmulator\CoaleseEqualTokenEmulator;
use PhpParser\Lexer\TokenEmulator\ExplicitOctalEmulator;
use PhpParser\Lexer\TokenEmulator\FlexibleDocStringEmulator;
use PhpParser\Lexer\TokenEmulator\FnTokenEmulator;
use PhpParser\Lexer\TokenEmulator\MatchTokenEmulator;
use PhpParser\Lexer\TokenEmulator\NullsafeTokenEmulator;
use PhpParser\Lexer\TokenEmulator\NumericLiteralSeparatorEmulator;
use PhpParser\Lexer\TokenEmulator\PipeOperatorEmulator;
use PhpParser\Lexer\TokenEmulator\PropertyTokenEmulator;
use PhpParser\Lexer\TokenEmulator\ReadonlyFunctionTokenEmulator;
use PhpParser\Lexer\TokenEmulator\ReadonlyTokenEmulator;
use PhpParser\Lexer\TokenEmulator\ReverseEmulator;
use PhpParser\Lexer\TokenEmulator\TokenEmulator;
use PhpParser\Lexer\TokenEmulator\VoidCastEmulator;
use PhpParser\PhpVersion;
use PhpParser\Token;
@@ -47,6 +47,10 @@ class Emulative extends Lexer {
new ReadonlyTokenEmulator(),
new ExplicitOctalEmulator(),
new ReadonlyFunctionTokenEmulator(),
new PropertyTokenEmulator(),
new AsymmetricVisibilityTokenEmulator(),
new PipeOperatorEmulator(),
new VoidCastEmulator(),
];
// Collect emulators that are relevant for the PHP version we're running

@@ -0,0 +1,93 @@
<?php declare(strict_types=1);
namespace PhpParser\Lexer\TokenEmulator;
use PhpParser\PhpVersion;
use PhpParser\Token;
final class AsymmetricVisibilityTokenEmulator extends TokenEmulator {
public function getPhpVersion(): PhpVersion {
return PhpVersion::fromComponents(8, 4);
}
public function isEmulationNeeded(string $code): bool {
$code = strtolower($code);
return strpos($code, 'public(set)') !== false ||
strpos($code, 'protected(set)') !== false ||
strpos($code, 'private(set)') !== false;
}
public function emulate(string $code, array $tokens): array {
$map = [
\T_PUBLIC => \T_PUBLIC_SET,
\T_PROTECTED => \T_PROTECTED_SET,
\T_PRIVATE => \T_PRIVATE_SET,
];
for ($i = 0, $c = count($tokens); $i < $c; ++$i) {
$token = $tokens[$i];
if (isset($map[$token->id]) && $i + 3 < $c && $tokens[$i + 1]->text === '(' &&
$tokens[$i + 2]->id === \T_STRING && \strtolower($tokens[$i + 2]->text) === 'set' &&
$tokens[$i + 3]->text === ')' &&
$this->isKeywordContext($tokens, $i)
) {
array_splice($tokens, $i, 4, [
new Token(
$map[$token->id], $token->text . '(' . $tokens[$i + 2]->text . ')',
$token->line, $token->pos),
]);
$c -= 3;
}
}
return $tokens;
}
public function reverseEmulate(string $code, array $tokens): array {
$reverseMap = [
\T_PUBLIC_SET => \T_PUBLIC,
\T_PROTECTED_SET => \T_PROTECTED,
\T_PRIVATE_SET => \T_PRIVATE,
];
for ($i = 0, $c = count($tokens); $i < $c; ++$i) {
$token = $tokens[$i];
if (isset($reverseMap[$token->id]) &&
\preg_match('/(public|protected|private)\((set)\)/i', $token->text, $matches)
) {
[, $modifier, $set] = $matches;
$modifierLen = \strlen($modifier);
array_splice($tokens, $i, 1, [
new Token($reverseMap[$token->id], $modifier, $token->line, $token->pos),
new Token(\ord('('), '(', $token->line, $token->pos + $modifierLen),
new Token(\T_STRING, $set, $token->line, $token->pos + $modifierLen + 1),
new Token(\ord(')'), ')', $token->line, $token->pos + $modifierLen + 4),
]);
$i += 3;
$c += 3;
}
}
return $tokens;
}
/** @param Token[] $tokens */
protected function isKeywordContext(array $tokens, int $pos): bool {
$prevToken = $this->getPreviousNonSpaceToken($tokens, $pos);
if ($prevToken === null) {
return false;
}
return $prevToken->id !== \T_OBJECT_OPERATOR
&& $prevToken->id !== \T_NULLSAFE_OBJECT_OPERATOR;
}
/** @param Token[] $tokens */
private function getPreviousNonSpaceToken(array $tokens, int $start): ?Token {
for ($i = $start - 1; $i >= 0; --$i) {
if ($tokens[$i]->id === T_WHITESPACE) {
continue;
}
return $tokens[$i];
}
return null;
}
}

@@ -14,8 +14,12 @@ abstract class KeywordEmulator extends TokenEmulator {
/** @param Token[] $tokens */
protected function isKeywordContext(array $tokens, int $pos): bool {
$previousNonSpaceToken = $this->getPreviousNonSpaceToken($tokens, $pos);
return $previousNonSpaceToken === null || $previousNonSpaceToken->id !== \T_OBJECT_OPERATOR;
$prevToken = $this->getPreviousNonSpaceToken($tokens, $pos);
if ($prevToken === null) {
return false;
}
return $prevToken->id !== \T_OBJECT_OPERATOR
&& $prevToken->id !== \T_NULLSAFE_OBJECT_OPERATOR;
}
public function emulate(string $code, array $tokens): array {

@@ -0,0 +1,45 @@
<?php declare(strict_types=1);
namespace PhpParser\Lexer\TokenEmulator;
use PhpParser\Lexer\TokenEmulator\TokenEmulator;
use PhpParser\PhpVersion;
use PhpParser\Token;
class PipeOperatorEmulator extends TokenEmulator {
public function getPhpVersion(): PhpVersion {
return PhpVersion::fromComponents(8, 5);
}
public function isEmulationNeeded(string $code): bool {
return \strpos($code, '|>') !== false;
}
public function emulate(string $code, array $tokens): array {
for ($i = 0, $c = count($tokens); $i < $c; ++$i) {
$token = $tokens[$i];
if ($token->text === '|' && isset($tokens[$i + 1]) && $tokens[$i + 1]->text === '>') {
array_splice($tokens, $i, 2, [
new Token(\T_PIPE, '|>', $token->line, $token->pos),
]);
$c--;
}
}
return $tokens;
}
public function reverseEmulate(string $code, array $tokens): array {
for ($i = 0, $c = count($tokens); $i < $c; ++$i) {
$token = $tokens[$i];
if ($token->id === \T_PIPE) {
array_splice($tokens, $i, 1, [
new Token(\ord('|'), '|', $token->line, $token->pos),
new Token(\ord('>'), '>', $token->line, $token->pos + 1),
]);
$i++;
$c++;
}
}
return $tokens;
}
}

@@ -0,0 +1,19 @@
<?php declare(strict_types=1);
namespace PhpParser\Lexer\TokenEmulator;
use PhpParser\PhpVersion;
final class PropertyTokenEmulator extends KeywordEmulator {
public function getPhpVersion(): PhpVersion {
return PhpVersion::fromComponents(8, 4);
}
public function getKeywordString(): string {
return '__property__';
}
public function getKeywordToken(): int {
return \T_PROPERTY_C;
}
}

@@ -0,0 +1,98 @@
<?php declare(strict_types=1);
namespace PhpParser\Lexer\TokenEmulator;
use PhpParser\PhpVersion;
use PhpParser\Token;
class VoidCastEmulator extends TokenEmulator {
public function getPhpVersion(): PhpVersion {
return PhpVersion::fromComponents(8, 5);
}
public function isEmulationNeeded(string $code): bool {
return (bool)\preg_match('/\([ \t]*void[ \t]*\)/i', $code);
}
public function emulate(string $code, array $tokens): array {
for ($i = 0, $c = count($tokens); $i < $c; ++$i) {
$token = $tokens[$i];
if ($token->text !== '(') {
continue;
}
$numTokens = 1;
$text = '(';
$j = $i + 1;
if ($j < $c && $tokens[$j]->id === \T_WHITESPACE && preg_match('/[ \t]+/', $tokens[$j]->text)) {
$text .= $tokens[$j]->text;
$numTokens++;
$j++;
}
if ($j >= $c || $tokens[$j]->id !== \T_STRING || \strtolower($tokens[$j]->text) !== 'void') {
continue;
}
$text .= $tokens[$j]->text;
$numTokens++;
$k = $j + 1;
if ($k < $c && $tokens[$k]->id === \T_WHITESPACE && preg_match('/[ \t]+/', $tokens[$k]->text)) {
$text .= $tokens[$k]->text;
$numTokens++;
$k++;
}
if ($k >= $c || $tokens[$k]->text !== ')') {
continue;
}
$text .= ')';
$numTokens++;
array_splice($tokens, $i, $numTokens, [
new Token(\T_VOID_CAST, $text, $token->line, $token->pos),
]);
$c -= $numTokens - 1;
}
return $tokens;
}
public function reverseEmulate(string $code, array $tokens): array {
for ($i = 0, $c = count($tokens); $i < $c; ++$i) {
$token = $tokens[$i];
if ($token->id !== \T_VOID_CAST) {
continue;
}
if (!preg_match('/^\(([ \t]*)(void)([ \t]*)\)$/i', $token->text, $match)) {
throw new \LogicException('Unexpected T_VOID_CAST contents');
}
$newTokens = [];
$pos = $token->pos;
$newTokens[] = new Token(\ord('('), '(', $token->line, $pos);
$pos++;
if ($match[1] !== '') {
$newTokens[] = new Token(\T_WHITESPACE, $match[1], $token->line, $pos);
$pos += \strlen($match[1]);
}
$newTokens[] = new Token(\T_STRING, $match[2], $token->line, $pos);
$pos += \strlen($match[2]);
if ($match[3] !== '') {
$newTokens[] = new Token(\T_WHITESPACE, $match[3], $token->line, $pos);
$pos += \strlen($match[3]);
}
$newTokens[] = new Token(\ord(')'), ')', $token->line, $pos);
array_splice($tokens, $i, 1, $newTokens);
$i += \count($newTokens) - 1;
$c += \count($newTokens) - 1;
}
return $tokens;
}
}

@@ -14,23 +14,47 @@ final class Modifiers {
public const ABSTRACT = 16;
public const FINAL = 32;
public const READONLY = 64;
public const PUBLIC_SET = 128;
public const PROTECTED_SET = 256;
public const PRIVATE_SET = 512;
public const VISIBILITY_MASK = 1 | 2 | 4;
public const VISIBILITY_MASK = self::PUBLIC | self::PROTECTED | self::PRIVATE;
public const VISIBILITY_SET_MASK = self::PUBLIC_SET | self::PROTECTED_SET | self::PRIVATE_SET;
private const TO_STRING_MAP = [
self::PUBLIC => 'public',
self::PROTECTED => 'protected',
self::PRIVATE => 'private',
self::STATIC => 'static',
self::ABSTRACT => 'abstract',
self::FINAL => 'final',
self::READONLY => 'readonly',
self::PUBLIC_SET => 'public(set)',
self::PROTECTED_SET => 'protected(set)',
self::PRIVATE_SET => 'private(set)',
];
public static function toString(int $modifier): string {
if (!isset(self::TO_STRING_MAP[$modifier])) {
throw new \InvalidArgumentException("Unknown modifier $modifier");
}
return self::TO_STRING_MAP[$modifier];
}
private static function isValidModifier(int $modifier): bool {
$isPow2 = ($modifier & ($modifier - 1)) == 0 && $modifier != 0;
return $isPow2 && $modifier <= self::PRIVATE_SET;
}
/**
* @internal
*/
public static function verifyClassModifier(int $a, int $b): void {
if ($a & Modifiers::ABSTRACT && $b & Modifiers::ABSTRACT) {
throw new Error('Multiple abstract modifiers are not allowed');
}
if ($a & Modifiers::FINAL && $b & Modifiers::FINAL) {
throw new Error('Multiple final modifiers are not allowed');
}
if ($a & Modifiers::READONLY && $b & Modifiers::READONLY) {
throw new Error('Multiple readonly modifiers are not allowed');
assert(self::isValidModifier($b));
if (($a & $b) != 0) {
throw new Error(
'Multiple ' . self::toString($b) . ' modifiers are not allowed');
}
if ($a & 48 && $b & 48) {
@@ -42,24 +66,16 @@ final class Modifiers {
* @internal
*/
public static function verifyModifier(int $a, int $b): void {
if ($a & Modifiers::VISIBILITY_MASK && $b & Modifiers::VISIBILITY_MASK) {
assert(self::isValidModifier($b));
if (($a & Modifiers::VISIBILITY_MASK && $b & Modifiers::VISIBILITY_MASK) ||
($a & Modifiers::VISIBILITY_SET_MASK && $b & Modifiers::VISIBILITY_SET_MASK)
) {
throw new Error('Multiple access type modifiers are not allowed');
}
if ($a & Modifiers::ABSTRACT && $b & Modifiers::ABSTRACT) {
throw new Error('Multiple abstract modifiers are not allowed');
}
if ($a & Modifiers::STATIC && $b & Modifiers::STATIC) {
throw new Error('Multiple static modifiers are not allowed');
}
if ($a & Modifiers::FINAL && $b & Modifiers::FINAL) {
throw new Error('Multiple final modifiers are not allowed');
}
if ($a & Modifiers::READONLY && $b & Modifiers::READONLY) {
throw new Error('Multiple readonly modifiers are not allowed');
if (($a & $b) != 0) {
throw new Error(
'Multiple ' . self::toString($b) . ' modifiers are not allowed');
}
if ($a & 48 && $b & 48) {

@@ -185,7 +185,7 @@ class NameContext {
// Check for relevant type-specific use statements
foreach ($this->origAliases[$type] as $alias => $orig) {
if ($type === Stmt\Use_::TYPE_CONSTANT) {
// Constants are are complicated-sensitive
// Constants are complicated-sensitive
$normalizedOrig = $this->normalizeConstName($orig->toString());
if ($normalizedOrig === $this->normalizeConstName($name)) {
$possibleNames[] = new Name($alias);

@@ -6,6 +6,7 @@ interface Node {
/**
* Gets the type of the node.
*
* @psalm-return non-empty-string
* @return string Type of the node
*/
public function getType(): string;
@@ -21,6 +22,7 @@ interface Node {
* Gets line the node started in (alias of getStartLine).
*
* @return int Start line (or -1 if not available)
* @phpstan-return -1|positive-int
*
* @deprecated Use getStartLine() instead
*/
@@ -32,6 +34,7 @@ interface Node {
* Requires the 'startLine' attribute to be enabled in the lexer (enabled by default).
*
* @return int Start line (or -1 if not available)
* @phpstan-return -1|positive-int
*/
public function getStartLine(): int;
@@ -41,6 +44,7 @@ interface Node {
* Requires the 'endLine' attribute to be enabled in the lexer (enabled by default).
*
* @return int End line (or -1 if not available)
* @phpstan-return -1|positive-int
*/
public function getEndLine(): int;

@@ -1,3 +1,11 @@
<?php declare(strict_types=1);
namespace PhpParser\Node\Expr;
require __DIR__ . '/../ArrayItem.php';
if (false) {
// For classmap-authoritative support.
class ArrayItem extends \PhpParser\Node\ArrayItem {
}
}

@@ -7,8 +7,10 @@ use PhpParser\Node\Expr;
use PhpParser\Node\FunctionLike;
class ArrowFunction extends Expr implements FunctionLike {
/** @var bool Whether the closure is static */
public bool $static;
/** @var bool Whether to return by reference */
public bool $byRef;
/** @var Node\Param[] */
@@ -17,6 +19,7 @@ class ArrowFunction extends Expr implements FunctionLike {
/** @var null|Node\Identifier|Node\Name|Node\ComplexType */
public ?Node $returnType;
/** @var Expr Expression body */
public Expr $expr;
/** @var Node\AttributeGroup[] */
public array $attrGroups;

@@ -0,0 +1,15 @@
<?php declare(strict_types=1);
namespace PhpParser\Node\Expr\BinaryOp;
use PhpParser\Node\Expr\BinaryOp;
class Pipe extends BinaryOp {
public function getOperatorSigil(): string {
return '|>';
}
public function getType(): string {
return 'Expr_BinaryOp_Pipe';
}
}

@@ -32,4 +32,29 @@ abstract class CallLike extends Expr {
assert(!$this->isFirstClassCallable());
return $this->getRawArgs();
}
/**
* Retrieves a specific argument from the raw arguments.
*
* Returns the named argument that matches the given `$name`, or the
* positional (unnamed) argument that exists at the given `$position`,
* otherwise, returns `null` for first-class callables or if no match is found.
*/
public function getArg(string $name, int $position): ?Arg {
if ($this->isFirstClassCallable()) {
return null;
}
foreach ($this->getRawArgs() as $i => $arg) {
if ($arg->unpack) {
continue;
}
if (
($arg->name !== null && $arg->name->toString() === $name)
|| ($arg->name === null && $i === $position)
) {
return $arg;
}
}
return null;
}
}

@@ -0,0 +1,11 @@
<?php declare(strict_types=1);
namespace PhpParser\Node\Expr\Cast;
use PhpParser\Node\Expr\Cast;
class Void_ extends Cast {
public function getType(): string {
return 'Expr_Cast_Void';
}
}

@@ -1,3 +1,11 @@
<?php declare(strict_types=1);
namespace PhpParser\Node\Expr;
require __DIR__ . '/../ClosureUse.php';
if (false) {
// For classmap-authoritative support.
class ClosureUse extends \PhpParser\Node\ClosureUse {
}
}

@@ -6,12 +6,15 @@ use PhpParser\Node;
use PhpParser\Node\MatchArm;
class Match_ extends Node\Expr {
/** @var Node\Expr Condition */
public Node\Expr $cond;
/** @var MatchArm[] */
public array $arms;
/**
* @param Node\Expr $cond Condition
* @param MatchArm[] $arms
* @param array<string, mixed> $attributes Additional attributes
*/
public function __construct(Node\Expr $cond, array $arms = [], array $attributes = []) {
$this->attributes = $attributes;

@@ -8,7 +8,10 @@ use PhpParser\NodeAbstract;
* Represents a non-namespaced name. Namespaced names are represented using Name nodes.
*/
class Identifier extends NodeAbstract {
/** @var string Identifier as string */
/**
* @psalm-var non-empty-string
* @var string Identifier as string
*/
public string $name;
/** @var array<string, bool> */
@@ -25,6 +28,10 @@ class Identifier extends NodeAbstract {
* @param array<string, mixed> $attributes Additional attributes
*/
public function __construct(string $name, array $attributes = []) {
if ($name === '') {
throw new \InvalidArgumentException('Identifier name cannot be empty');
}
$this->attributes = $attributes;
$this->name = $name;
}
@@ -36,6 +43,7 @@ class Identifier extends NodeAbstract {
/**
* Get identifier as string.
*
* @psalm-return non-empty-string
* @return string Identifier as string.
*/
public function toString(): string {
@@ -45,6 +53,7 @@ class Identifier extends NodeAbstract {
/**
* Get lowercased identifier as string.
*
* @psalm-return non-empty-string&lowercase-string
* @return string Lowercased identifier as string
*/
public function toLowerString(): string {
@@ -63,6 +72,7 @@ class Identifier extends NodeAbstract {
/**
* Get identifier as string.
*
* @psalm-return non-empty-string
* @return string Identifier as string
*/
public function __toString(): string {

@@ -8,7 +8,6 @@ use PhpParser\NodeAbstract;
class MatchArm extends NodeAbstract {
/** @var null|list<Node\Expr> */
public ?array $conds;
/** @var Node\Expr */
public Expr $body;
/**

@@ -5,7 +5,10 @@ namespace PhpParser\Node;
use PhpParser\NodeAbstract;
class Name extends NodeAbstract {
/** @var string Name as string */
/**
* @psalm-var non-empty-string
* @var string Name as string
*/
public string $name;
/** @var array<string, bool> */
@@ -33,6 +36,7 @@ class Name extends NodeAbstract {
/**
* Get parts of name (split by the namespace separator).
*
* @psalm-return non-empty-list<string>
* @return string[] Parts of name
*/
public function getParts(): array {
@@ -103,6 +107,7 @@ class Name extends NodeAbstract {
* Returns a string representation of the name itself, without taking the name type into
* account (e.g., not including a leading backslash for fully qualified names).
*
* @psalm-return non-empty-string
* @return string String representation
*/
public function toString(): string {
@@ -113,6 +118,7 @@ class Name extends NodeAbstract {
* Returns a string representation of the name as it would occur in code (e.g., including
* leading backslash for fully qualified names.
*
* @psalm-return non-empty-string
* @return string String representation
*/
public function toCodeString(): string {
@@ -123,6 +129,7 @@ class Name extends NodeAbstract {
* Returns lowercased string representation of the name, without taking the name type into
* account (e.g., no leading backslash for fully qualified names).
*
* @psalm-return non-empty-string&lowercase-string
* @return string Lowercased string representation
*/
public function toLowerString(): string {
@@ -142,6 +149,7 @@ class Name extends NodeAbstract {
* Returns a string representation of the name by imploding the namespace parts with the
* namespace separator.
*
* @psalm-return non-empty-string
* @return string String representation
*/
public function __toString(): string {
@@ -237,6 +245,7 @@ class Name extends NodeAbstract {
*
* @param string|string[]|self $name Name to prepare
*
* @psalm-return non-empty-string
* @return string Prepared name
*/
private static function prepareName($name): string {

@@ -17,9 +17,12 @@ class Param extends NodeAbstract {
public Expr $var;
/** @var null|Expr Default value */
public ?Expr $default;
/** @var int Optional visibility flags */
public int $flags;
/** @var AttributeGroup[] PHP attribute groups */
public array $attrGroups;
/** @var PropertyHook[] Property hooks for promoted properties */
public array $hooks;
/**
* Constructs a parameter node.
@@ -32,13 +35,15 @@ class Param extends NodeAbstract {
* @param array<string, mixed> $attributes Additional attributes
* @param int $flags Optional visibility flags
* @param list<AttributeGroup> $attrGroups PHP attribute groups
* @param PropertyHook[] $hooks Property hooks for promoted properties
*/
public function __construct(
Expr $var, ?Expr $default = null, ?Node $type = null,
bool $byRef = false, bool $variadic = false,
array $attributes = [],
int $flags = 0,
array $attrGroups = []
array $attrGroups = [],
array $hooks = []
) {
$this->attributes = $attributes;
$this->type = $type;
@@ -48,10 +53,11 @@ class Param extends NodeAbstract {
$this->default = $default;
$this->flags = $flags;
$this->attrGroups = $attrGroups;
$this->hooks = $hooks;
}
public function getSubNodeNames(): array {
return ['attrGroups', 'flags', 'type', 'byRef', 'variadic', 'var', 'default'];
return ['attrGroups', 'flags', 'type', 'byRef', 'variadic', 'var', 'default', 'hooks'];
}
public function getType(): string {
@@ -62,11 +68,20 @@ class Param extends NodeAbstract {
* Whether this parameter uses constructor property promotion.
*/
public function isPromoted(): bool {
return $this->flags !== 0;
return $this->flags !== 0 || $this->hooks !== [];
}
public function isPublic(): bool {
return (bool) ($this->flags & Modifiers::PUBLIC);
$public = (bool) ($this->flags & Modifiers::PUBLIC);
if ($public) {
return true;
}
if ($this->hooks === []) {
return false;
}
return ($this->flags & Modifiers::VISIBILITY_MASK) === 0;
}
public function isProtected(): bool {
@@ -80,4 +95,25 @@ class Param extends NodeAbstract {
public function isReadonly(): bool {
return (bool) ($this->flags & Modifiers::READONLY);
}
/**
* Whether the promoted property has explicit public(set) visibility.
*/
public function isPublicSet(): bool {
return (bool) ($this->flags & Modifiers::PUBLIC_SET);
}
/**
* Whether the promoted property has explicit protected(set) visibility.
*/
public function isProtectedSet(): bool {
return (bool) ($this->flags & Modifiers::PROTECTED_SET);
}
/**
* Whether the promoted property has explicit private(set) visibility.
*/
public function isPrivateSet(): bool {
return (bool) ($this->flags & Modifiers::PRIVATE_SET);
}
}

@@ -0,0 +1,105 @@
<?php declare(strict_types=1);
namespace PhpParser\Node;
use PhpParser\Modifiers;
use PhpParser\Node\Expr\Assign;
use PhpParser\Node\Expr\PropertyFetch;
use PhpParser\Node\Expr\Variable;
use PhpParser\Node\Stmt\Expression;
use PhpParser\Node\Stmt\Return_;
use PhpParser\NodeAbstract;
class PropertyHook extends NodeAbstract implements FunctionLike {
/** @var AttributeGroup[] PHP attribute groups */
public array $attrGroups;
/** @var int Modifiers */
public int $flags;
/** @var bool Whether hook returns by reference */
public bool $byRef;
/** @var Identifier Hook name */
public Identifier $name;
/** @var Param[] Parameters */
public array $params;
/** @var null|Expr|Stmt[] Hook body */
public $body;
/**
* Constructs a property hook node.
*
* @param string|Identifier $name Hook name
* @param null|Expr|Stmt[] $body Hook body
* @param array{
* flags?: int,
* byRef?: bool,
* params?: Param[],
* attrGroups?: AttributeGroup[],
* } $subNodes Array of the following optional subnodes:
* 'flags => 0 : Flags
* 'byRef' => false : Whether hook returns by reference
* 'params' => array(): Parameters
* 'attrGroups' => array(): PHP attribute groups
* @param array<string, mixed> $attributes Additional attributes
*/
public function __construct($name, $body, array $subNodes = [], array $attributes = []) {
$this->attributes = $attributes;
$this->name = \is_string($name) ? new Identifier($name) : $name;
$this->body = $body;
$this->flags = $subNodes['flags'] ?? 0;
$this->byRef = $subNodes['byRef'] ?? false;
$this->params = $subNodes['params'] ?? [];
$this->attrGroups = $subNodes['attrGroups'] ?? [];
}
public function returnsByRef(): bool {
return $this->byRef;
}
public function getParams(): array {
return $this->params;
}
public function getReturnType() {
return null;
}
/**
* Whether the property hook is final.
*/
public function isFinal(): bool {
return (bool) ($this->flags & Modifiers::FINAL);
}
public function getStmts(): ?array {
if ($this->body instanceof Expr) {
$name = $this->name->toLowerString();
if ($name === 'get') {
return [new Return_($this->body)];
}
if ($name === 'set') {
if (!$this->hasAttribute('propertyName')) {
throw new \LogicException(
'Can only use getStmts() on a "set" hook if the "propertyName" attribute is set');
}
$propName = $this->getAttribute('propertyName');
$prop = new PropertyFetch(new Variable('this'), (string) $propName);
return [new Expression(new Assign($prop, $this->body))];
}
throw new \LogicException('Unknown property hook "' . $name . '"');
}
return $this->body;
}
public function getAttrGroups(): array {
return $this->attrGroups;
}
public function getType(): string {
return 'PropertyHook';
}
public function getSubNodeNames(): array {
return ['attrGroups', 'flags', 'byRef', 'name', 'params', 'body'];
}
}

@@ -3,8 +3,9 @@
namespace PhpParser\Node;
use PhpParser\Node;
use PhpParser\NodeAbstract;
class PropertyItem extends Node\Stmt {
class PropertyItem extends NodeAbstract {
/** @var Node\VarLikeIdentifier Name */
public VarLikeIdentifier $name;
/** @var null|Node\Expr Default */

@@ -1,3 +1,11 @@
<?php declare(strict_types=1);
namespace PhpParser\Node\Scalar;
require __DIR__ . '/Float_.php';
if (false) {
// For classmap-authoritative support.
class DNumber extends Float_ {
}
}

@@ -1,3 +1,11 @@
<?php declare(strict_types=1);
namespace PhpParser\Node\Scalar;
require __DIR__ . '/InterpolatedString.php';
if (false) {
// For classmap-authoritative support.
class Encapsed extends InterpolatedString {
}
}

@@ -1,3 +1,13 @@
<?php declare(strict_types=1);
namespace PhpParser\Node\Scalar;
use PhpParser\Node\InterpolatedStringPart;
require __DIR__ . '/../InterpolatedStringPart.php';
if (false) {
// For classmap-authoritative support.
class EncapsedStringPart extends InterpolatedStringPart {
}
}

@@ -1,3 +1,11 @@
<?php declare(strict_types=1);
namespace PhpParser\Node\Scalar;
require __DIR__ . '/Int_.php';
if (false) {
// For classmap-authoritative support.
class LNumber extends Int_ {
}
}

@@ -0,0 +1,15 @@
<?php declare(strict_types=1);
namespace PhpParser\Node\Scalar\MagicConst;
use PhpParser\Node\Scalar\MagicConst;
class Property extends MagicConst {
public function getName(): string {
return '__PROPERTY__';
}
public function getType(): string {
return 'Scalar_MagicConst_Property';
}
}

@@ -0,0 +1,29 @@
<?php declare(strict_types=1);
namespace PhpParser\Node\Stmt;
use PhpParser\Node\Stmt;
class Block extends Stmt {
/** @var Stmt[] Statements */
public array $stmts;
/**
* A block of statements.
*
* @param Stmt[] $stmts Statements
* @param array<string, mixed> $attributes Additional attributes
*/
public function __construct(array $stmts, array $attributes = []) {
$this->attributes = $attributes;
$this->stmts = $stmts;
}
public function getType(): string {
return 'Stmt_Block';
}
public function getSubNodeNames(): array {
return ['stmts'];
}
}

@@ -17,7 +17,7 @@ abstract class ClassLike extends Node\Stmt {
public ?Node\Name $namespacedName;
/**
* @return TraitUse[]
* @return list<TraitUse>
*/
public function getTraitUses(): array {
$traitUses = [];
@@ -30,7 +30,7 @@ abstract class ClassLike extends Node\Stmt {
}
/**
* @return ClassConst[]
* @return list<ClassConst>
*/
public function getConstants(): array {
$constants = [];
@@ -43,7 +43,7 @@ abstract class ClassLike extends Node\Stmt {
}
/**
* @return Property[]
* @return list<Property>
*/
public function getProperties(): array {
$properties = [];
@@ -78,7 +78,7 @@ abstract class ClassLike extends Node\Stmt {
/**
* Gets all methods defined directly in this class/interface/trait
*
* @return ClassMethod[]
* @return list<ClassMethod>
*/
public function getMethods(): array {
$methods = [];

@@ -7,20 +7,28 @@ use PhpParser\Node;
class Const_ extends Node\Stmt {
/** @var Node\Const_[] Constant declarations */
public array $consts;
/** @var Node\AttributeGroup[] PHP attribute groups */
public array $attrGroups;
/**
* Constructs a const list node.
*
* @param Node\Const_[] $consts Constant declarations
* @param array<string, mixed> $attributes Additional attributes
* @param list<Node\AttributeGroup> $attrGroups PHP attribute groups
*/
public function __construct(array $consts, array $attributes = []) {
public function __construct(
array $consts,
array $attributes = [],
array $attrGroups = []
) {
$this->attributes = $attributes;
$this->attrGroups = $attrGroups;
$this->consts = $consts;
}
public function getSubNodeNames(): array {
return ['consts'];
return ['attrGroups', 'consts'];
}
public function getType(): string {

@@ -1,3 +1,13 @@
<?php declare(strict_types=1);
namespace PhpParser\Node\Stmt;
use PhpParser\Node\DeclareItem;
require __DIR__ . '/../DeclareItem.php';
if (false) {
// For classmap-authoritative support.
class DeclareDeclare extends DeclareItem {
}
}

@@ -18,6 +18,8 @@ class Property extends Node\Stmt {
public ?Node $type;
/** @var Node\AttributeGroup[] PHP attribute groups */
public array $attrGroups;
/** @var Node\PropertyHook[] Property hooks */
public array $hooks;
/**
* Constructs a class property list node.
@@ -27,17 +29,19 @@ class Property extends Node\Stmt {
* @param array<string, mixed> $attributes Additional attributes
* @param null|Identifier|Name|ComplexType $type Type declaration
* @param Node\AttributeGroup[] $attrGroups PHP attribute groups
* @param Node\PropertyHook[] $hooks Property hooks
*/
public function __construct(int $flags, array $props, array $attributes = [], ?Node $type = null, array $attrGroups = []) {
public function __construct(int $flags, array $props, array $attributes = [], ?Node $type = null, array $attrGroups = [], array $hooks = []) {
$this->attributes = $attributes;
$this->flags = $flags;
$this->props = $props;
$this->type = $type;
$this->attrGroups = $attrGroups;
$this->hooks = $hooks;
}
public function getSubNodeNames(): array {
return ['attrGroups', 'flags', 'type', 'props'];
return ['attrGroups', 'flags', 'type', 'props', 'hooks'];
}
/**
@@ -76,6 +80,41 @@ class Property extends Node\Stmt {
return (bool) ($this->flags & Modifiers::READONLY);
}
/**
* Whether the property is abstract.
*/
public function isAbstract(): bool {
return (bool) ($this->flags & Modifiers::ABSTRACT);
}
/**
* Whether the property is final.
*/
public function isFinal(): bool {
return (bool) ($this->flags & Modifiers::FINAL);
}
/**
* Whether the property has explicit public(set) visibility.
*/
public function isPublicSet(): bool {
return (bool) ($this->flags & Modifiers::PUBLIC_SET);
}
/**
* Whether the property has explicit protected(set) visibility.
*/
public function isProtectedSet(): bool {
return (bool) ($this->flags & Modifiers::PROTECTED_SET);
}
/**
* Whether the property has explicit private(set) visibility.
*/
public function isPrivateSet(): bool {
return (bool) ($this->flags & Modifiers::PRIVATE_SET);
}
public function getType(): string {
return 'Stmt_Property';
}

@@ -1,3 +1,13 @@
<?php declare(strict_types=1);
namespace PhpParser\Node\Stmt;
use PhpParser\Node\PropertyItem;
require __DIR__ . '/../PropertyItem.php';
if (false) {
// For classmap-authoritative support.
class PropertyProperty extends PropertyItem {
}
}

@@ -1,3 +1,11 @@
<?php declare(strict_types=1);
namespace PhpParser\Node\Stmt;
require __DIR__ . '/../StaticVar.php';
if (false) {
// For classmap-authoritative support.
class StaticVar extends \PhpParser\Node\StaticVar {
}
}

@@ -1,29 +0,0 @@
<?php declare(strict_types=1);
namespace PhpParser\Node\Stmt;
use PhpParser\Node;
class Throw_ extends Node\Stmt {
/** @var Node\Expr Expression */
public Node\Expr $expr;
/**
* Constructs a legacy throw statement node.
*
* @param Node\Expr $expr Expression
* @param array<string, mixed> $attributes Additional attributes
*/
public function __construct(Node\Expr $expr, array $attributes = []) {
$this->attributes = $attributes;
$this->expr = $expr;
}
public function getSubNodeNames(): array {
return ['expr'];
}
public function getType(): string {
return 'Stmt_Throw';
}
}

@@ -1,3 +1,13 @@
<?php declare(strict_types=1);
namespace PhpParser\Node\Stmt;
use PhpParser\Node\UseItem;
require __DIR__ . '/../UseItem.php';
if (false) {
// For classmap-authoritative support.
class UseUse extends UseItem {
}
}

@@ -3,9 +3,10 @@
namespace PhpParser\Node;
use PhpParser\Node;
use PhpParser\NodeAbstract;
use PhpParser\Node\Stmt\Use_;
class UseItem extends Node\Stmt {
class UseItem extends NodeAbstract {
/**
* @var Use_::TYPE_* One of the Stmt\Use_::TYPE_* constants. Will only differ from TYPE_UNKNOWN for mixed group uses
*/

@@ -19,6 +19,7 @@ abstract class NodeAbstract implements Node, \JsonSerializable {
* Gets line the node started in (alias of getStartLine).
*
* @return int Start line (or -1 if not available)
* @phpstan-return -1|positive-int
*/
public function getLine(): int {
return $this->attributes['startLine'] ?? -1;
@@ -30,6 +31,7 @@ abstract class NodeAbstract implements Node, \JsonSerializable {
* Requires the 'startLine' attribute to be enabled in the lexer (enabled by default).
*
* @return int Start line (or -1 if not available)
* @phpstan-return -1|positive-int
*/
public function getStartLine(): int {
return $this->attributes['startLine'] ?? -1;
@@ -41,6 +43,7 @@ abstract class NodeAbstract implements Node, \JsonSerializable {
* Requires the 'endLine' attribute to be enabled in the lexer (enabled by default).
*
* @return int End line (or -1 if not available)
* @phpstan-return -1|positive-int
*/
public function getEndLine(): int {
return $this->attributes['endLine'] ?? -1;

@@ -2,7 +2,12 @@
namespace PhpParser;
use PhpParser\Node\Expr\Array_;
use PhpParser\Node\Expr\Include_;
use PhpParser\Node\Expr\List_;
use PhpParser\Node\Scalar\Int_;
use PhpParser\Node\Scalar\InterpolatedString;
use PhpParser\Node\Scalar\String_;
use PhpParser\Node\Stmt\GroupUse;
use PhpParser\Node\Stmt\Use_;
use PhpParser\Node\UseItem;
@@ -10,7 +15,20 @@ use PhpParser\Node\UseItem;
class NodeDumper {
private bool $dumpComments;
private bool $dumpPositions;
private bool $dumpOtherAttributes;
private ?string $code;
private string $res;
private string $nl;
private const IGNORE_ATTRIBUTES = [
'comments' => true,
'startLine' => true,
'endLine' => true,
'startFilePos' => true,
'endFilePos' => true,
'startTokenPos' => true,
'endTokenPos' => true,
];
/**
* Constructs a NodeDumper.
@@ -19,12 +37,14 @@ class NodeDumper {
* * bool dumpComments: Whether comments should be dumped.
* * bool dumpPositions: Whether line/offset information should be dumped. To dump offset
* information, the code needs to be passed to dump().
* * bool dumpOtherAttributes: Whether non-comment, non-position attributes should be dumped.
*
* @param array $options Options (see description)
*/
public function __construct(array $options = []) {
$this->dumpComments = !empty($options['dumpComments']);
$this->dumpPositions = !empty($options['dumpPositions']);
$this->dumpOtherAttributes = !empty($options['dumpOtherAttributes']);
}
/**
@@ -39,72 +59,107 @@ class NodeDumper {
*/
public function dump($node, ?string $code = null): string {
$this->code = $code;
return $this->dumpRecursive($node);
$this->res = '';
$this->nl = "\n";
$this->dumpRecursive($node, false);
return $this->res;
}
/** @param Node|Comment|array $node */
protected function dumpRecursive($node): string {
/** @param mixed $node */
protected function dumpRecursive($node, bool $indent = true): void {
if ($indent) {
$this->nl .= " ";
}
if ($node instanceof Node) {
$r = $node->getType();
$this->res .= $node->getType();
if ($this->dumpPositions && null !== $p = $this->dumpPosition($node)) {
$r .= $p;
$this->res .= $p;
}
$r .= '(';
$this->res .= '(';
foreach ($node->getSubNodeNames() as $key) {
$r .= "\n " . $key . ': ';
$this->res .= "$this->nl " . $key . ': ';
$value = $node->$key;
if (null === $value) {
$r .= 'null';
} elseif (false === $value) {
$r .= 'false';
} elseif (true === $value) {
$r .= 'true';
} elseif (is_scalar($value)) {
if (\is_int($value)) {
if ('flags' === $key || 'newModifier' === $key) {
$r .= $this->dumpFlags($value);
} elseif ('type' === $key && $node instanceof Include_) {
$r .= $this->dumpIncludeType($value);
} elseif ('type' === $key
&& ($node instanceof Use_ || $node instanceof UseItem || $node instanceof GroupUse)) {
$r .= $this->dumpUseType($value);
} else {
$r .= $value;
$this->res .= $this->dumpFlags($value);
continue;
}
if ('type' === $key && $node instanceof Include_) {
$this->res .= $this->dumpIncludeType($value);
continue;
}
if ('type' === $key
&& ($node instanceof Use_ || $node instanceof UseItem || $node instanceof GroupUse)) {
$this->res .= $this->dumpUseType($value);
continue;
}
} else {
$r .= str_replace("\n", "\n ", $this->dumpRecursive($value));
}
$this->dumpRecursive($value);
}
if ($this->dumpComments && $comments = $node->getComments()) {
$r .= "\n comments: " . str_replace("\n", "\n ", $this->dumpRecursive($comments));
$this->res .= "$this->nl comments: ";
$this->dumpRecursive($comments);
}
} elseif (is_array($node)) {
$r = 'array(';
foreach ($node as $key => $value) {
$r .= "\n " . $key . ': ';
if ($this->dumpOtherAttributes) {
foreach ($node->getAttributes() as $key => $value) {
if (isset(self::IGNORE_ATTRIBUTES[$key])) {
continue;
}
if (null === $value) {
$r .= 'null';
} elseif (false === $value) {
$r .= 'false';
} elseif (true === $value) {
$r .= 'true';
} elseif (is_scalar($value)) {
$r .= $value;
} else {
$r .= str_replace("\n", "\n ", $this->dumpRecursive($value));
$this->res .= "$this->nl $key: ";
if (\is_int($value)) {
if ('kind' === $key) {
if ($node instanceof Int_) {
$this->res .= $this->dumpIntKind($value);
continue;
}
if ($node instanceof String_ || $node instanceof InterpolatedString) {
$this->res .= $this->dumpStringKind($value);
continue;
}
if ($node instanceof Array_) {
$this->res .= $this->dumpArrayKind($value);
continue;
}
if ($node instanceof List_) {
$this->res .= $this->dumpListKind($value);
continue;
}
}
}
$this->dumpRecursive($value);
}
}
$this->res .= "$this->nl)";
} elseif (\is_array($node)) {
$this->res .= 'array(';
foreach ($node as $key => $value) {
$this->res .= "$this->nl " . $key . ': ';
$this->dumpRecursive($value);
}
$this->res .= "$this->nl)";
} elseif ($node instanceof Comment) {
return $node->getReformattedText();
$this->res .= \str_replace("\n", $this->nl, $node->getReformattedText());
} elseif (\is_string($node)) {
$this->res .= \str_replace("\n", $this->nl, $node);
} elseif (\is_int($node) || \is_float($node)) {
$this->res .= $node;
} elseif (null === $node) {
$this->res .= 'null';
} elseif (false === $node) {
$this->res .= 'false';
} elseif (true === $node) {
$this->res .= 'true';
} else {
throw new \InvalidArgumentException('Can only dump nodes and arrays.');
}
return $r . "\n)";
if ($indent) {
$this->nl = \substr($this->nl, 0, -4);
}
}
protected function dumpFlags(int $flags): string {
@@ -130,6 +185,15 @@ class NodeDumper {
if ($flags & Modifiers::READONLY) {
$strs[] = 'READONLY';
}
if ($flags & Modifiers::PUBLIC_SET) {
$strs[] = 'PUBLIC_SET';
}
if ($flags & Modifiers::PROTECTED_SET) {
$strs[] = 'PROTECTED_SET';
}
if ($flags & Modifiers::PRIVATE_SET) {
$strs[] = 'PRIVATE_SET';
}
if ($strs) {
return implode(' | ', $strs) . ' (' . $flags . ')';
@@ -138,32 +202,62 @@ class NodeDumper {
}
}
protected function dumpIncludeType(int $type): string {
$map = [
/** @param array<int, string> $map */
private function dumpEnum(int $value, array $map): string {
if (!isset($map[$value])) {
return (string) $value;
}
return $map[$value] . ' (' . $value . ')';
}
private function dumpIncludeType(int $type): string {
return $this->dumpEnum($type, [
Include_::TYPE_INCLUDE => 'TYPE_INCLUDE',
Include_::TYPE_INCLUDE_ONCE => 'TYPE_INCLUDE_ONCE',
Include_::TYPE_REQUIRE => 'TYPE_REQUIRE',
Include_::TYPE_REQUIRE_ONCE => 'TYPE_REQUIRE_ONCE',
];
if (!isset($map[$type])) {
return (string) $type;
}
return $map[$type] . ' (' . $type . ')';
]);
}
protected function dumpUseType(int $type): string {
$map = [
private function dumpUseType(int $type): string {
return $this->dumpEnum($type, [
Use_::TYPE_UNKNOWN => 'TYPE_UNKNOWN',
Use_::TYPE_NORMAL => 'TYPE_NORMAL',
Use_::TYPE_FUNCTION => 'TYPE_FUNCTION',
Use_::TYPE_CONSTANT => 'TYPE_CONSTANT',
];
]);
}
if (!isset($map[$type])) {
return (string) $type;
}
return $map[$type] . ' (' . $type . ')';
private function dumpIntKind(int $kind): string {
return $this->dumpEnum($kind, [
Int_::KIND_BIN => 'KIND_BIN',
Int_::KIND_OCT => 'KIND_OCT',
Int_::KIND_DEC => 'KIND_DEC',
Int_::KIND_HEX => 'KIND_HEX',
]);
}
private function dumpStringKind(int $kind): string {
return $this->dumpEnum($kind, [
String_::KIND_SINGLE_QUOTED => 'KIND_SINGLE_QUOTED',
String_::KIND_DOUBLE_QUOTED => 'KIND_DOUBLE_QUOTED',
String_::KIND_HEREDOC => 'KIND_HEREDOC',
String_::KIND_NOWDOC => 'KIND_NOWDOC',
]);
}
private function dumpArrayKind(int $kind): string {
return $this->dumpEnum($kind, [
Array_::KIND_LONG => 'KIND_LONG',
Array_::KIND_SHORT => 'KIND_SHORT',
]);
}
private function dumpListKind(int $kind): string {
return $this->dumpEnum($kind, [
List_::KIND_LIST => 'KIND_LIST',
List_::KIND_ARRAY => 'KIND_ARRAY',
]);
}
/**

@@ -15,6 +15,10 @@ class NodeFinder {
* @return Node[] Found nodes satisfying the filter callback
*/
public function find($nodes, callable $filter): array {
if ($nodes === []) {
return [];
}
if (!is_array($nodes)) {
$nodes = [$nodes];
}
@@ -52,6 +56,10 @@ class NodeFinder {
* @return null|Node Found node (or null if none found)
*/
public function findFirst($nodes, callable $filter): ?Node {
if ($nodes === []) {
return null;
}
if (!is_array($nodes)) {
$nodes = [$nodes];
}

@@ -34,7 +34,7 @@ class NodeTraverser implements NodeTraverserInterface {
*
* @param NodeVisitor ...$visitors Node visitors
*/
public function __construct(NodeVisitor... $visitors) {
public function __construct(NodeVisitor ...$visitors) {
$this->visitors = $visitors;
}
@@ -99,66 +99,72 @@ class NodeTraverser implements NodeTraverserInterface {
if ($this->stopTraversal) {
break;
}
} elseif ($subNode instanceof Node) {
$traverseChildren = true;
$visitorIndex = -1;
foreach ($this->visitors as $visitorIndex => $visitor) {
$return = $visitor->enterNode($subNode);
if (null !== $return) {
if ($return instanceof Node) {
$this->ensureReplacementReasonable($subNode, $return);
$subNode = $node->$name = $return;
} elseif (NodeVisitor::DONT_TRAVERSE_CHILDREN === $return) {
$traverseChildren = false;
} elseif (NodeVisitor::DONT_TRAVERSE_CURRENT_AND_CHILDREN === $return) {
$traverseChildren = false;
break;
} elseif (NodeVisitor::STOP_TRAVERSAL === $return) {
$this->stopTraversal = true;
break 2;
} elseif (NodeVisitor::REPLACE_WITH_NULL === $return) {
$node->$name = null;
continue 2;
} else {
throw new \LogicException(
'enterNode() returned invalid value of type ' . gettype($return)
);
}
}
}
continue;
}
if ($traverseChildren) {
$this->traverseNode($subNode);
if ($this->stopTraversal) {
if (!$subNode instanceof Node) {
continue;
}
$traverseChildren = true;
$visitorIndex = -1;
foreach ($this->visitors as $visitorIndex => $visitor) {
$return = $visitor->enterNode($subNode);
if (null !== $return) {
if ($return instanceof Node) {
$this->ensureReplacementReasonable($subNode, $return);
$subNode = $node->$name = $return;
} elseif (NodeVisitor::DONT_TRAVERSE_CHILDREN === $return) {
$traverseChildren = false;
} elseif (NodeVisitor::DONT_TRAVERSE_CURRENT_AND_CHILDREN === $return) {
$traverseChildren = false;
break;
} elseif (NodeVisitor::STOP_TRAVERSAL === $return) {
$this->stopTraversal = true;
break 2;
} elseif (NodeVisitor::REPLACE_WITH_NULL === $return) {
$node->$name = null;
continue 2;
} else {
throw new \LogicException(
'enterNode() returned invalid value of type ' . gettype($return)
);
}
}
}
for (; $visitorIndex >= 0; --$visitorIndex) {
$visitor = $this->visitors[$visitorIndex];
$return = $visitor->leaveNode($subNode);
if ($traverseChildren) {
$this->traverseNode($subNode);
if ($this->stopTraversal) {
break;
}
}
if (null !== $return) {
if ($return instanceof Node) {
$this->ensureReplacementReasonable($subNode, $return);
$subNode = $node->$name = $return;
} elseif (NodeVisitor::STOP_TRAVERSAL === $return) {
$this->stopTraversal = true;
break 2;
} elseif (NodeVisitor::REPLACE_WITH_NULL === $return) {
$node->$name = null;
break;
} elseif (\is_array($return)) {
throw new \LogicException(
'leaveNode() may only return an array ' .
'if the parent structure is an array'
);
} else {
throw new \LogicException(
'leaveNode() returned invalid value of type ' . gettype($return)
);
}
for (; $visitorIndex >= 0; --$visitorIndex) {
$visitor = $this->visitors[$visitorIndex];
$return = $visitor->leaveNode($subNode);
if (null !== $return) {
if ($return instanceof Node) {
$this->ensureReplacementReasonable($subNode, $return);
$subNode = $node->$name = $return;
} elseif (NodeVisitor::STOP_TRAVERSAL === $return) {
$this->stopTraversal = true;
break 2;
} elseif (NodeVisitor::REPLACE_WITH_NULL === $return) {
$node->$name = null;
break;
} elseif (\is_array($return)) {
throw new \LogicException(
'leaveNode() may only return an array ' .
'if the parent structure is an array'
);
} else {
throw new \LogicException(
'leaveNode() returned invalid value of type ' . gettype($return)
);
}
}
}
@@ -175,78 +181,81 @@ class NodeTraverser implements NodeTraverserInterface {
protected function traverseArray(array $nodes): array {
$doNodes = [];
foreach ($nodes as $i => &$node) {
if ($node instanceof Node) {
$traverseChildren = true;
$visitorIndex = -1;
foreach ($this->visitors as $visitorIndex => $visitor) {
$return = $visitor->enterNode($node);
if (null !== $return) {
if ($return instanceof Node) {
$this->ensureReplacementReasonable($node, $return);
$node = $return;
} elseif (\is_array($return)) {
$doNodes[] = [$i, $return];
continue 2;
} elseif (NodeVisitor::REMOVE_NODE === $return) {
$doNodes[] = [$i, []];
continue 2;
} elseif (NodeVisitor::DONT_TRAVERSE_CHILDREN === $return) {
$traverseChildren = false;
} elseif (NodeVisitor::DONT_TRAVERSE_CURRENT_AND_CHILDREN === $return) {
$traverseChildren = false;
break;
} elseif (NodeVisitor::STOP_TRAVERSAL === $return) {
$this->stopTraversal = true;
break 2;
} elseif (NodeVisitor::REPLACE_WITH_NULL === $return) {
throw new \LogicException(
'REPLACE_WITH_NULL can not be used if the parent structure is an array');
} else {
throw new \LogicException(
'enterNode() returned invalid value of type ' . gettype($return)
);
}
}
foreach ($nodes as $i => $node) {
if (!$node instanceof Node) {
if (\is_array($node)) {
throw new \LogicException('Invalid node structure: Contains nested arrays');
}
continue;
}
if ($traverseChildren) {
$this->traverseNode($node);
if ($this->stopTraversal) {
$traverseChildren = true;
$visitorIndex = -1;
foreach ($this->visitors as $visitorIndex => $visitor) {
$return = $visitor->enterNode($node);
if (null !== $return) {
if ($return instanceof Node) {
$this->ensureReplacementReasonable($node, $return);
$nodes[$i] = $node = $return;
} elseif (\is_array($return)) {
$doNodes[] = [$i, $return];
continue 2;
} elseif (NodeVisitor::REMOVE_NODE === $return) {
$doNodes[] = [$i, []];
continue 2;
} elseif (NodeVisitor::DONT_TRAVERSE_CHILDREN === $return) {
$traverseChildren = false;
} elseif (NodeVisitor::DONT_TRAVERSE_CURRENT_AND_CHILDREN === $return) {
$traverseChildren = false;
break;
} elseif (NodeVisitor::STOP_TRAVERSAL === $return) {
$this->stopTraversal = true;
break 2;
} elseif (NodeVisitor::REPLACE_WITH_NULL === $return) {
throw new \LogicException(
'REPLACE_WITH_NULL can not be used if the parent structure is an array');
} else {
throw new \LogicException(
'enterNode() returned invalid value of type ' . gettype($return)
);
}
}
}
for (; $visitorIndex >= 0; --$visitorIndex) {
$visitor = $this->visitors[$visitorIndex];
$return = $visitor->leaveNode($node);
if ($traverseChildren) {
$this->traverseNode($node);
if ($this->stopTraversal) {
break;
}
}
if (null !== $return) {
if ($return instanceof Node) {
$this->ensureReplacementReasonable($node, $return);
$node = $return;
} elseif (\is_array($return)) {
$doNodes[] = [$i, $return];
break;
} elseif (NodeVisitor::REMOVE_NODE === $return) {
$doNodes[] = [$i, []];
break;
} elseif (NodeVisitor::STOP_TRAVERSAL === $return) {
$this->stopTraversal = true;
break 2;
} elseif (NodeVisitor::REPLACE_WITH_NULL === $return) {
throw new \LogicException(
'REPLACE_WITH_NULL can not be used if the parent structure is an array');
} else {
throw new \LogicException(
'leaveNode() returned invalid value of type ' . gettype($return)
);
}
for (; $visitorIndex >= 0; --$visitorIndex) {
$visitor = $this->visitors[$visitorIndex];
$return = $visitor->leaveNode($node);
if (null !== $return) {
if ($return instanceof Node) {
$this->ensureReplacementReasonable($node, $return);
$nodes[$i] = $node = $return;
} elseif (\is_array($return)) {
$doNodes[] = [$i, $return];
break;
} elseif (NodeVisitor::REMOVE_NODE === $return) {
$doNodes[] = [$i, []];
break;
} elseif (NodeVisitor::STOP_TRAVERSAL === $return) {
$this->stopTraversal = true;
break 2;
} elseif (NodeVisitor::REPLACE_WITH_NULL === $return) {
throw new \LogicException(
'REPLACE_WITH_NULL can not be used if the parent structure is an array');
} else {
throw new \LogicException(
'leaveNode() returned invalid value of type ' . gettype($return)
);
}
}
} elseif (\is_array($node)) {
throw new \LogicException('Invalid node structure: Contains nested arrays');
}
}

@@ -0,0 +1,82 @@
<?php declare(strict_types=1);
namespace PhpParser\NodeVisitor;
use PhpParser\Comment;
use PhpParser\Node;
use PhpParser\NodeVisitorAbstract;
use PhpParser\Token;
class CommentAnnotatingVisitor extends NodeVisitorAbstract {
/** @var int Last seen token start position */
private int $pos = 0;
/** @var Token[] Token array */
private array $tokens;
/** @var list<int> Token positions of comments */
private array $commentPositions = [];
/**
* Create a comment annotation visitor.
*
* @param Token[] $tokens Token array
*/
public function __construct(array $tokens) {
$this->tokens = $tokens;
// Collect positions of comments. We use this to avoid traversing parts of the AST where
// there are no comments.
foreach ($tokens as $i => $token) {
if ($token->id === \T_COMMENT || $token->id === \T_DOC_COMMENT) {
$this->commentPositions[] = $i;
}
}
}
public function enterNode(Node $node) {
$nextCommentPos = current($this->commentPositions);
if ($nextCommentPos === false) {
// No more comments.
return self::STOP_TRAVERSAL;
}
$oldPos = $this->pos;
$this->pos = $pos = $node->getStartTokenPos();
if ($nextCommentPos > $oldPos && $nextCommentPos < $pos) {
$comments = [];
while (--$pos >= $oldPos) {
$token = $this->tokens[$pos];
if ($token->id === \T_DOC_COMMENT) {
$comments[] = new Comment\Doc(
$token->text, $token->line, $token->pos, $pos,
$token->getEndLine(), $token->getEndPos() - 1, $pos);
continue;
}
if ($token->id === \T_COMMENT) {
$comments[] = new Comment(
$token->text, $token->line, $token->pos, $pos,
$token->getEndLine(), $token->getEndPos() - 1, $pos);
continue;
}
if ($token->id !== \T_WHITESPACE) {
break;
}
}
if (!empty($comments)) {
$node->setAttribute('comments', array_reverse($comments));
}
do {
$nextCommentPos = next($this->commentPositions);
} while ($nextCommentPos !== false && $nextCommentPos < $this->pos);
}
$endPos = $node->getEndTokenPos();
if ($nextCommentPos > $endPos) {
// Skip children if there are no comments located inside this node.
$this->pos = $endPos;
return self::DONT_TRAVERSE_CHILDREN;
}
return null;
}
}

@@ -12,7 +12,7 @@ use PhpParser\NodeVisitorAbstract;
class FindingVisitor extends NodeVisitorAbstract {
/** @var callable Filter callback */
protected $filterCallback;
/** @var Node[] Found nodes */
/** @var list<Node> Found nodes */
protected array $foundNodes;
public function __construct(callable $filterCallback) {
@@ -24,7 +24,7 @@ class FindingVisitor extends NodeVisitorAbstract {
*
* Nodes are returned in pre-order.
*
* @return Node[] Found nodes
* @return list<Node> Found nodes
*/
public function getFoundNodes(): array {
return $this->foundNodes;

@@ -110,11 +110,21 @@ class NameResolver extends NodeVisitorAbstract {
$node->type = $this->resolveType($node->type);
}
$this->resolveAttrGroups($node);
} elseif ($node instanceof Node\PropertyHook) {
foreach ($node->params as $param) {
$param->type = $this->resolveType($param->type);
$this->resolveAttrGroups($param);
}
$this->resolveAttrGroups($node);
} elseif ($node instanceof Stmt\Const_) {
foreach ($node->consts as $const) {
$this->addNamespacedName($const);
}
$this->resolveAttrGroups($node);
} elseif ($node instanceof Stmt\ClassConst) {
if (null !== $node->type) {
$node->type = $this->resolveType($node->type);
}
$this->resolveAttrGroups($node);
} elseif ($node instanceof Stmt\EnumCase) {
$this->resolveAttrGroups($node);

@@ -9,10 +9,12 @@ use PhpParser\NodeVisitorAbstract;
* Visitor that connects a child node to its parent node
* as well as its sibling nodes.
*
* On the child node, the parent node can be accessed through
* With <code>$weakReferences=false</code> on the child node, the parent node can be accessed through
* <code>$node->getAttribute('parent')</code>, the previous
* node can be accessed through <code>$node->getAttribute('previous')</code>,
* and the next node can be accessed through <code>$node->getAttribute('next')</code>.
*
* With <code>$weakReferences=true</code> attribute names are prefixed by "weak_", e.g. "weak_parent".
*/
final class NodeConnectingVisitor extends NodeVisitorAbstract {
/**
@@ -25,6 +27,12 @@ final class NodeConnectingVisitor extends NodeVisitorAbstract {
*/
private $previous;
private bool $weakReferences;
public function __construct(bool $weakReferences = false) {
$this->weakReferences = $weakReferences;
}
public function beforeTraverse(array $nodes) {
$this->stack = [];
$this->previous = null;
@@ -32,12 +40,26 @@ final class NodeConnectingVisitor extends NodeVisitorAbstract {
public function enterNode(Node $node) {
if (!empty($this->stack)) {
$node->setAttribute('parent', $this->stack[count($this->stack) - 1]);
$parent = $this->stack[count($this->stack) - 1];
if ($this->weakReferences) {
$node->setAttribute('weak_parent', \WeakReference::create($parent));
} else {
$node->setAttribute('parent', $parent);
}
}
if ($this->previous !== null && $this->previous->getAttribute('parent') === $node->getAttribute('parent')) {
$node->setAttribute('previous', $this->previous);
$this->previous->setAttribute('next', $node);
if ($this->previous !== null) {
if (
$this->weakReferences
) {
if ($this->previous->getAttribute('weak_parent') === $node->getAttribute('weak_parent')) {
$node->setAttribute('weak_previous', \WeakReference::create($this->previous));
$this->previous->setAttribute('weak_next', \WeakReference::create($node));
}
} elseif ($this->previous->getAttribute('parent') === $node->getAttribute('parent')) {
$node->setAttribute('previous', $this->previous);
$this->previous->setAttribute('next', $node);
}
}
$this->stack[] = $node;

@@ -11,8 +11,10 @@ use function count;
/**
* Visitor that connects a child node to its parent node.
*
* On the child node, the parent node can be accessed through
* With <code>$weakReferences=false</code> on the child node, the parent node can be accessed through
* <code>$node->getAttribute('parent')</code>.
*
* With <code>$weakReferences=true</code> the attribute name is "weak_parent" instead.
*/
final class ParentConnectingVisitor extends NodeVisitorAbstract {
/**
@@ -20,13 +22,24 @@ final class ParentConnectingVisitor extends NodeVisitorAbstract {
*/
private array $stack = [];
private bool $weakReferences;
public function __construct(bool $weakReferences = false) {
$this->weakReferences = $weakReferences;
}
public function beforeTraverse(array $nodes) {
$this->stack = [];
}
public function enterNode(Node $node) {
if (!empty($this->stack)) {
$node->setAttribute('parent', $this->stack[count($this->stack) - 1]);
$parent = $this->stack[count($this->stack) - 1];
if ($this->weakReferences) {
$node->setAttribute('weak_parent', \WeakReference::create($parent));
} else {
$node->setAttribute('parent', $parent);
}
}
$this->stack[] = $node;

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

@@ -7,6 +7,7 @@ namespace PhpParser;
* turn is based on work by Masato Bito.
*/
use PhpParser\Node\Arg;
use PhpParser\Node\Expr;
use PhpParser\Node\Expr\Array_;
use PhpParser\Node\Expr\Cast\Double;
@@ -14,6 +15,7 @@ use PhpParser\Node\Identifier;
use PhpParser\Node\InterpolatedStringPart;
use PhpParser\Node\Name;
use PhpParser\Node\Param;
use PhpParser\Node\PropertyHook;
use PhpParser\Node\Scalar\InterpolatedString;
use PhpParser\Node\Scalar\Int_;
use PhpParser\Node\Scalar\String_;
@@ -21,6 +23,7 @@ use PhpParser\Node\Stmt;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\ClassConst;
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\Node\Stmt\Const_;
use PhpParser\Node\Stmt\Else_;
use PhpParser\Node\Stmt\ElseIf_;
use PhpParser\Node\Stmt\Enum_;
@@ -30,6 +33,8 @@ use PhpParser\Node\Stmt\Nop;
use PhpParser\Node\Stmt\Property;
use PhpParser\Node\Stmt\TryCatch;
use PhpParser\Node\UseItem;
use PhpParser\Node\VarLikeIdentifier;
use PhpParser\NodeVisitor\CommentAnnotatingVisitor;
abstract class ParserAbstract implements Parser {
private const SYMBOL_NONE = -1;
@@ -201,6 +206,11 @@ abstract class ParserAbstract implements Parser {
$this->semValue = null;
$this->createdArrays = null;
if ($result !== null) {
$traverser = new NodeTraverser(new CommentAnnotatingVisitor($this->tokens));
$traverser->traverse($result);
}
return $result;
}
@@ -312,7 +322,7 @@ abstract class ParserAbstract implements Parser {
try {
$callback = $this->reduceCallbacks[$rule];
if ($callback !== null) {
$callback($stackPos);
$callback($this, $stackPos);
} elseif ($ruleLength > 0) {
$this->semValue = $this->semStack[$stackPos - $ruleLength + 1];
}
@@ -403,8 +413,6 @@ abstract class ParserAbstract implements Parser {
$rule = $state - $this->numNonLeafStates;
}
}
throw new \RuntimeException('Reached end of parser loop');
}
protected function emitError(Error $error): void {
@@ -473,7 +481,7 @@ abstract class ParserAbstract implements Parser {
protected function getAttributes(int $tokenStartPos, int $tokenEndPos): array {
$startToken = $this->tokens[$tokenStartPos];
$afterEndToken = $this->tokens[$tokenEndPos + 1];
$attributes = [
return [
'startLine' => $startToken->line,
'startTokenPos' => $tokenStartPos,
'startFilePos' => $startToken->pos,
@@ -481,11 +489,6 @@ abstract class ParserAbstract implements Parser {
'endTokenPos' => $tokenEndPos,
'endFilePos' => $afterEndToken->pos - 1,
];
$comments = $this->getCommentsBeforeToken($tokenStartPos);
if (!empty($comments)) {
$attributes['comments'] = $comments;
}
return $attributes;
}
/**
@@ -500,7 +503,7 @@ abstract class ParserAbstract implements Parser {
// Get attributes for the sentinel token.
$token = $this->tokens[$tokenPos];
$attributes = [
return [
'startLine' => $token->line,
'startTokenPos' => $tokenPos,
'startFilePos' => $token->pos,
@@ -508,11 +511,6 @@ abstract class ParserAbstract implements Parser {
'endTokenPos' => $tokenPos,
'endFilePos' => $token->pos,
];
$comments = $this->getCommentsBeforeToken($tokenPos);
if (!empty($comments)) {
$attributes['comments'] = $comments;
}
return $attributes;
}
/*
@@ -585,7 +583,7 @@ abstract class ParserAbstract implements Parser {
} else {
// For semicolon namespaces we have to move the statements after a namespace declaration into ->stmts
$resultStmts = [];
$targetStmts =& $resultStmts;
$targetStmts = &$resultStmts;
$lastNs = null;
foreach ($stmts as $stmt) {
if ($stmt instanceof Node\Stmt\Namespace_) {
@@ -594,12 +592,12 @@ abstract class ParserAbstract implements Parser {
}
if ($stmt->stmts === null) {
$stmt->stmts = [];
$targetStmts =& $stmt->stmts;
$targetStmts = &$stmt->stmts;
$resultStmts[] = $stmt;
} else {
// This handles the invalid case of mixed style namespaces
$resultStmts[] = $stmt;
$targetStmts =& $resultStmts;
$targetStmts = &$resultStmts;
}
$lastNs = $stmt;
} elseif ($stmt instanceof Node\Stmt\HaltCompiler) {
@@ -845,6 +843,7 @@ abstract class ParserAbstract implements Parser {
if (\is_string($contents)) {
if ($contents === '') {
$attributes['rawValue'] = $contents;
return new String_('', $attributes);
}
@@ -852,6 +851,7 @@ abstract class ParserAbstract implements Parser {
$contents, $indentLen, $indentChar, true, true, $attributes
);
$contents = preg_replace('~(\r\n|\n|\r)\z~', '', $contents);
$attributes['rawValue'] = $contents;
if ($kind === String_::KIND_HEREDOC) {
$contents = String_::parseEscapeSequences($contents, null, $parseUnicodeEscape);
@@ -878,6 +878,7 @@ abstract class ParserAbstract implements Parser {
if ($isLast) {
$part->value = preg_replace('~(\r\n|\n|\r)\z~', '', $part->value);
}
$part->setAttribute('rawValue', $part->value);
$part->value = String_::parseEscapeSequences($part->value, null, $parseUnicodeEscape);
if ('' === $part->value) {
continue;
@@ -899,12 +900,9 @@ abstract class ParserAbstract implements Parser {
}
/**
* Get comments before the given token position.
*
* @return Comment[] Comments
* Get last comment before the given token position, if any
*/
protected function getCommentsBeforeToken(int $tokenPos): array {
$comments = [];
protected function getCommentBeforeToken(int $tokenPos): ?Comment {
while (--$tokenPos >= 0) {
$token = $this->tokens[$tokenPos];
if (!isset($this->dropTokens[$token->id])) {
@@ -912,22 +910,21 @@ abstract class ParserAbstract implements Parser {
}
if ($token->id === \T_COMMENT || $token->id === \T_DOC_COMMENT) {
$comments[] = $this->createCommentFromToken($token, $tokenPos);
return $this->createCommentFromToken($token, $tokenPos);
}
}
return \array_reverse($comments);
return null;
}
/**
* Create a zero-length nop to capture preceding comments, if any.
*/
protected function maybeCreateZeroLengthNop(int $tokenPos): ?Nop {
$comments = $this->getCommentsBeforeToken($tokenPos);
if (empty($comments)) {
$comment = $this->getCommentBeforeToken($tokenPos);
if ($comment === null) {
return null;
}
$comment = $comments[\count($comments) - 1];
$commentEndLine = $comment->getEndLine();
$commentEndFilePos = $comment->getEndFilePos();
$commentEndTokenPos = $comment->getEndTokenPos();
@@ -938,14 +935,12 @@ abstract class ParserAbstract implements Parser {
'endFilePos' => $commentEndFilePos,
'startTokenPos' => $commentEndTokenPos + 1,
'endTokenPos' => $commentEndTokenPos,
'comments' => $comments,
];
return new Nop($attributes);
}
protected function maybeCreateNop(int $tokenStartPos, int $tokenEndPos): ?Nop {
$comments = $this->getCommentsBeforeToken($tokenStartPos);
if (empty($comments)) {
if ($this->getCommentBeforeToken($tokenStartPos) === null) {
return null;
}
return new Nop($this->getAttributes($tokenStartPos, $tokenEndPos));
@@ -1144,32 +1139,12 @@ abstract class ParserAbstract implements Parser {
}
protected function checkClassConst(ClassConst $node, int $modifierPos): void {
if ($node->flags & Modifiers::STATIC) {
$this->emitError(new Error(
"Cannot use 'static' as constant modifier",
$this->getAttributesAt($modifierPos)));
}
if ($node->flags & Modifiers::ABSTRACT) {
$this->emitError(new Error(
"Cannot use 'abstract' as constant modifier",
$this->getAttributesAt($modifierPos)));
}
if ($node->flags & Modifiers::READONLY) {
$this->emitError(new Error(
"Cannot use 'readonly' as constant modifier",
$this->getAttributesAt($modifierPos)));
}
}
protected function checkProperty(Property $node, int $modifierPos): void {
if ($node->flags & Modifiers::ABSTRACT) {
$this->emitError(new Error('Properties cannot be declared abstract',
$this->getAttributesAt($modifierPos)));
}
if ($node->flags & Modifiers::FINAL) {
$this->emitError(new Error('Properties cannot be declared final',
$this->getAttributesAt($modifierPos)));
foreach ([Modifiers::STATIC, Modifiers::ABSTRACT, Modifiers::READONLY] as $modifier) {
if ($node->flags & $modifier) {
$this->emitError(new Error(
"Cannot use '" . Modifiers::toString($modifier) . "' as constant modifier",
$this->getAttributesAt($modifierPos)));
}
}
}
@@ -1185,6 +1160,96 @@ abstract class ParserAbstract implements Parser {
}
}
protected function checkPropertyHooksForMultiProperty(Property $property, int $hookPos): void {
if (count($property->props) > 1) {
$this->emitError(new Error(
'Cannot use hooks when declaring multiple properties', $this->getAttributesAt($hookPos)));
}
}
/** @param PropertyHook[] $hooks */
protected function checkEmptyPropertyHookList(array $hooks, int $hookPos): void {
if (empty($hooks)) {
$this->emitError(new Error(
'Property hook list cannot be empty', $this->getAttributesAt($hookPos)));
}
}
protected function checkPropertyHook(PropertyHook $hook, ?int $paramListPos): void {
$name = $hook->name->toLowerString();
if ($name !== 'get' && $name !== 'set') {
$this->emitError(new Error(
'Unknown hook "' . $hook->name . '", expected "get" or "set"',
$hook->name->getAttributes()));
}
if ($name === 'get' && $paramListPos !== null) {
$this->emitError(new Error(
'get hook must not have a parameter list', $this->getAttributesAt($paramListPos)));
}
}
protected function checkPropertyHookModifiers(int $a, int $b, int $modifierPos): void {
try {
Modifiers::verifyModifier($a, $b);
} catch (Error $error) {
$error->setAttributes($this->getAttributesAt($modifierPos));
$this->emitError($error);
}
if ($b != Modifiers::FINAL) {
$this->emitError(new Error(
'Cannot use the ' . Modifiers::toString($b) . ' modifier on a property hook',
$this->getAttributesAt($modifierPos)));
}
}
protected function checkConstantAttributes(Const_ $node): void {
if ($node->attrGroups !== [] && count($node->consts) > 1) {
$this->emitError(new Error(
'Cannot use attributes on multiple constants at once', $node->getAttributes()));
}
}
/**
* @param Property|Param $node
*/
protected function addPropertyNameToHooks(Node $node): void {
if ($node instanceof Property) {
$name = $node->props[0]->name->toString();
} else {
$name = $node->var->name;
}
foreach ($node->hooks as $hook) {
$hook->setAttribute('propertyName', $name);
}
}
/** @param array<Node\Arg|Node\VariadicPlaceholder> $args */
private function isSimpleExit(array $args): bool {
if (\count($args) === 0) {
return true;
}
if (\count($args) === 1) {
$arg = $args[0];
return $arg instanceof Arg && $arg->name === null &&
$arg->byRef === false && $arg->unpack === false;
}
return false;
}
/**
* @param array<Node\Arg|Node\VariadicPlaceholder> $args
* @param array<string, mixed> $attrs
*/
protected function createExitExpr(string $name, int $namePos, array $args, array $attrs): Expr {
if ($this->isSimpleExit($args)) {
// Create Exit node for backwards compatibility.
$attrs['kind'] = strtolower($name) === 'exit' ? Expr\Exit_::KIND_EXIT : Expr\Exit_::KIND_DIE;
return new Expr\Exit_(\count($args) === 1 ? $args[0]->value : null, $attrs);
}
return new Expr\FuncCall(new Name($name, $this->getAttributesAt($namePos)), $args, $attrs);
}
/**
* Creates the token map.
*
@@ -1197,42 +1262,23 @@ abstract class ParserAbstract implements Parser {
protected function createTokenMap(): array {
$tokenMap = [];
for ($i = 0; $i < 1000; ++$i) {
if ($i < 256) {
// Single-char tokens use an identity mapping.
$tokenMap[$i] = $i;
} elseif (\T_DOUBLE_COLON === $i) {
// T_DOUBLE_COLON is equivalent to T_PAAMAYIM_NEKUDOTAYIM
$tokenMap[$i] = static::T_PAAMAYIM_NEKUDOTAYIM;
} elseif (\T_OPEN_TAG_WITH_ECHO === $i) {
// T_OPEN_TAG_WITH_ECHO with dropped T_OPEN_TAG results in T_ECHO
$tokenMap[$i] = static::T_ECHO;
} elseif (\T_CLOSE_TAG === $i) {
// T_CLOSE_TAG is equivalent to ';'
$tokenMap[$i] = ord(';');
} elseif ('UNKNOWN' !== $name = token_name($i)) {
if (defined($name = static::class . '::' . $name)) {
// Other tokens can be mapped directly
$tokenMap[$i] = constant($name);
}
// Single-char tokens use an identity mapping.
for ($i = 0; $i < 256; ++$i) {
$tokenMap[$i] = $i;
}
foreach ($this->symbolToName as $name) {
if ($name[0] === 'T') {
$tokenMap[\constant($name)] = constant(static::class . '::' . $name);
}
}
// Assign tokens for which we define compatibility constants, as token_name() does not know them.
$tokenMap[\T_FN] = static::T_FN;
$tokenMap[\T_COALESCE_EQUAL] = static::T_COALESCE_EQUAL;
$tokenMap[\T_NAME_QUALIFIED] = static::T_NAME_QUALIFIED;
$tokenMap[\T_NAME_FULLY_QUALIFIED] = static::T_NAME_FULLY_QUALIFIED;
$tokenMap[\T_NAME_RELATIVE] = static::T_NAME_RELATIVE;
$tokenMap[\T_MATCH] = static::T_MATCH;
$tokenMap[\T_NULLSAFE_OBJECT_OPERATOR] = static::T_NULLSAFE_OBJECT_OPERATOR;
$tokenMap[\T_ATTRIBUTE] = static::T_ATTRIBUTE;
$tokenMap[\T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG] = static::T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG;
$tokenMap[\T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG] = static::T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG;
$tokenMap[\T_ENUM] = static::T_ENUM;
$tokenMap[\T_READONLY] = static::T_READONLY;
// T_OPEN_TAG_WITH_ECHO with dropped T_OPEN_TAG results in T_ECHO
$tokenMap[\T_OPEN_TAG_WITH_ECHO] = static::T_ECHO;
// T_CLOSE_TAG is equivalent to ';'
$tokenMap[\T_CLOSE_TAG] = ord(';');
// We have create a map from PHP token IDs to external symbol IDs.
// We have created a map from PHP token IDs to external symbol IDs.
// Now map them to the internal symbol ID.
$fullTokenMap = [];
foreach ($tokenMap as $phpToken => $extSymbol) {

@@ -6,34 +6,6 @@ use PhpParser\Parser\Php7;
use PhpParser\Parser\Php8;
class ParserFactory {
public const PREFER_PHP7 = 1;
public const ONLY_PHP7 = 3;
/**
* Creates a Parser instance, according to the provided kind.
*
* @param int $kind One of ::PREFER_PHP7 or ::ONLY_PHP7
* @param Lexer|null $lexer Lexer to use. Defaults to emulative lexer when not specified
*
* @return Parser The parser instance
*
* @deprecated Use createForVersion(), createForNewestSupportedVersion() or createForHostVersion() instead.
*/
public function create(int $kind, ?Lexer $lexer = null): Parser {
if (null === $lexer) {
$lexer = new Lexer\Emulative();
}
switch ($kind) {
case self::PREFER_PHP7:
case self::ONLY_PHP7:
return new Parser\Php7($lexer);
default:
throw new \LogicException(
'Kind must be one of ::PREFER_PHP7 or ::ONLY_PHP7'
);
}
}
/**
* Create a parser targeting the given version on a best-effort basis. The parser will generally
* accept code for the newest supported version, but will try to accommodate code that becomes

@@ -43,7 +43,7 @@ class PhpVersion {
* if it is still under development.
*/
public static function getNewestSupported(): self {
return self::fromComponents(8, 2);
return self::fromComponents(8, 5);
}
/**
@@ -149,9 +149,23 @@ class PhpVersion {
}
/**
* Whether this version support yield in expression context without parentheses.
* Whether this version supports yield in expression context without parentheses.
*/
public function supportsYieldWithoutParentheses(): bool {
return $this->id >= 70000;
}
/**
* Whether this version supports unicode escape sequences in strings.
*/
public function supportsUnicodeEscapes(): bool {
return $this->id >= 70000;
}
/*
* Whether this version supports attributes.
*/
public function supportsAttributes(): bool {
return $this->id >= 80000;
}
}

@@ -17,13 +17,14 @@ class Standard extends PrettyPrinterAbstract {
// Special nodes
protected function pParam(Node\Param $node): string {
return $this->pAttrGroups($node->attrGroups, true)
return $this->pAttrGroups($node->attrGroups, $this->phpVersion->supportsAttributes())
. $this->pModifiers($node->flags)
. ($node->type ? $this->p($node->type) . ' ' : '')
. ($node->byRef ? '&' : '')
. ($node->variadic ? '...' : '')
. $this->p($node->var)
. ($node->default ? ' = ' . $this->p($node->default) : '');
. ($node->default ? ' = ' . $this->p($node->default) : '')
. ($node->hooks ? ' {' . $this->pStmts($node->hooks) . $this->nl . '}' : '');
}
protected function pArg(Node\Arg $node): string {
@@ -125,6 +126,10 @@ class Standard extends PrettyPrinterAbstract {
return '__TRAIT__';
}
protected function pScalar_MagicConst_Property(MagicConst\Property $node): string {
return '__PROPERTY__';
}
// Scalars
private function indentString(string $str): string {
@@ -420,6 +425,10 @@ class Standard extends PrettyPrinterAbstract {
return $this->pInfixOp(BinaryOp\Coalesce::class, $node->left, ' ?? ', $node->right, $precedence, $lhsPrecedence);
}
protected function pExpr_BinaryOp_Pipe(BinaryOp\Pipe $node, int $precedence, int $lhsPrecedence): string {
return $this->pInfixOp(BinaryOp\Pipe::class, $node->left, ' |> ', $node->right, $precedence, $lhsPrecedence);
}
protected function pExpr_Instanceof(Expr\Instanceof_ $node, int $precedence, int $lhsPrecedence): string {
return $this->pPostfixOp(
Expr\Instanceof_::class, $node->expr,
@@ -512,6 +521,10 @@ class Standard extends PrettyPrinterAbstract {
return $this->pPrefixOp(Cast\Unset_::class, '(unset) ', $node->expr, $precedence, $lhsPrecedence);
}
protected function pExpr_Cast_Void(Cast\Void_ $node, int $precedence, int $lhsPrecedence): string {
return $this->pPrefixOp(Cast\Void_::class, '(void) ', $node->expr, $precedence, $lhsPrecedence);
}
// Function calls and similar constructs
protected function pExpr_FuncCall(Expr\FuncCall $node): string {
@@ -651,7 +664,7 @@ class Standard extends PrettyPrinterAbstract {
return $this->pAttrGroups($node->attrGroups, true)
. $this->pStatic($node->static)
. 'function ' . ($node->byRef ? '&' : '')
. '(' . $this->pMaybeMultiline($node->params, $this->phpVersion->supportsTrailingCommaInParamList()) . ')'
. '(' . $this->pParams($node->params) . ')'
. (!empty($node->uses) ? ' use (' . $this->pCommaSeparated($node->uses) . ')' : '')
. (null !== $node->returnType ? ': ' . $this->p($node->returnType) : '')
. ' {' . $this->pStmts($node->stmts) . $this->nl . '}';
@@ -683,7 +696,7 @@ class Standard extends PrettyPrinterAbstract {
$this->pAttrGroups($node->attrGroups, true)
. $this->pStatic($node->static)
. 'fn' . ($node->byRef ? '&' : '')
. '(' . $this->pMaybeMultiline($node->params, $this->phpVersion->supportsTrailingCommaInParamList()) . ')'
. '(' . $this->pParams($node->params) . ')'
. (null !== $node->returnType ? ': ' . $this->p($node->returnType) : '')
. ' => ',
$node->expr, $precedence, $lhsPrecedence);
@@ -827,7 +840,8 @@ class Standard extends PrettyPrinterAbstract {
return $this->pAttrGroups($node->attrGroups)
. (0 === $node->flags ? 'var ' : $this->pModifiers($node->flags))
. ($node->type ? $this->p($node->type) . ' ' : '')
. $this->pCommaSeparated($node->props) . ';';
. $this->pCommaSeparated($node->props)
. ($node->hooks ? ' {' . $this->pStmts($node->hooks) . $this->nl . '}' : ';');
}
protected function pPropertyItem(Node\PropertyItem $node): string {
@@ -835,11 +849,20 @@ class Standard extends PrettyPrinterAbstract {
. (null !== $node->default ? ' = ' . $this->p($node->default) : '');
}
protected function pPropertyHook(Node\PropertyHook $node): string {
return $this->pAttrGroups($node->attrGroups)
. $this->pModifiers($node->flags)
. ($node->byRef ? '&' : '') . $node->name
. ($node->params ? '(' . $this->pParams($node->params) . ')' : '')
. (\is_array($node->body) ? ' {' . $this->pStmts($node->body) . $this->nl . '}'
: ($node->body !== null ? ' => ' . $this->p($node->body) : '') . ';');
}
protected function pStmt_ClassMethod(Stmt\ClassMethod $node): string {
return $this->pAttrGroups($node->attrGroups)
. $this->pModifiers($node->flags)
. 'function ' . ($node->byRef ? '&' : '') . $node->name
. '(' . $this->pMaybeMultiline($node->params, $this->phpVersion->supportsTrailingCommaInParamList()) . ')'
. '(' . $this->pParams($node->params) . ')'
. (null !== $node->returnType ? ': ' . $this->p($node->returnType) : '')
. (null !== $node->stmts
? $this->nl . '{' . $this->pStmts($node->stmts) . $this->nl . '}'
@@ -857,13 +880,15 @@ class Standard extends PrettyPrinterAbstract {
protected function pStmt_Function(Stmt\Function_ $node): string {
return $this->pAttrGroups($node->attrGroups)
. 'function ' . ($node->byRef ? '&' : '') . $node->name
. '(' . $this->pMaybeMultiline($node->params, $this->phpVersion->supportsTrailingCommaInParamList()) . ')'
. '(' . $this->pParams($node->params) . ')'
. (null !== $node->returnType ? ': ' . $this->p($node->returnType) : '')
. $this->nl . '{' . $this->pStmts($node->stmts) . $this->nl . '}';
}
protected function pStmt_Const(Stmt\Const_ $node): string {
return 'const ' . $this->pCommaSeparated($node->consts) . ';';
return $this->pAttrGroups($node->attrGroups)
. 'const '
. $this->pCommaSeparated($node->consts) . ';';
}
protected function pStmt_Declare(Stmt\Declare_ $node): string {
@@ -960,10 +985,6 @@ class Standard extends PrettyPrinterAbstract {
return 'return' . (null !== $node->expr ? ' ' . $this->p($node->expr) : '') . ';';
}
protected function pStmt_Throw(Stmt\Throw_ $node): string {
return 'throw ' . $this->p($node->expr) . ';';
}
protected function pStmt_Label(Stmt\Label $node): string {
return $node->name . ':';
}
@@ -1012,6 +1033,10 @@ class Standard extends PrettyPrinterAbstract {
return '';
}
protected function pStmt_Block(Stmt\Block $node): string {
return '{' . $this->pStmts($node->stmts) . $this->nl . '}';
}
// Helpers
protected function pClassCommon(Stmt\Class_ $node, string $afterClassToken): string {
@@ -1164,6 +1189,27 @@ class Standard extends PrettyPrinterAbstract {
}
}
/** @param Node\Param[] $params
*/
private function hasParamWithAttributes(array $params): bool {
foreach ($params as $param) {
if ($param->attrGroups) {
return true;
}
}
return false;
}
/** @param Node\Param[] $params */
protected function pParams(array $params): string {
if ($this->hasNodeWithComments($params) ||
($this->hasParamWithAttributes($params) && !$this->phpVersion->supportsAttributes())
) {
return $this->pCommaSeparatedMultiline($params, $this->phpVersion->supportsTrailingCommaInParamList()) . $this->nl;
}
return $this->pCommaSeparated($params);
}
/** @param Node\AttributeGroup[] $nodes */
protected function pAttrGroups(array $nodes, bool $inline = false): string {
$result = '';

@@ -14,6 +14,7 @@ use PhpParser\Node\Expr\Cast;
use PhpParser\Node\IntersectionType;
use PhpParser\Node\MatchArm;
use PhpParser\Node\Param;
use PhpParser\Node\PropertyHook;
use PhpParser\Node\Scalar;
use PhpParser\Node\Stmt;
use PhpParser\Node\UnionType;
@@ -58,9 +59,11 @@ abstract class PrettyPrinterAbstract implements PrettyPrinter {
BinaryOp\Mod::class => [ 40, 41, 40],
BinaryOp\Plus::class => [ 50, 51, 50],
BinaryOp\Minus::class => [ 50, 51, 50],
// FIXME: This precedence is incorrect for PHP 8.
BinaryOp\Concat::class => [ 50, 51, 50],
BinaryOp\ShiftLeft::class => [ 60, 61, 60],
BinaryOp\ShiftRight::class => [ 60, 61, 60],
BinaryOp\Pipe::class => [ 65, 66, 65],
BinaryOp\Smaller::class => [ 70, 70, 70],
BinaryOp\SmallerOrEqual::class => [ 70, 70, 70],
BinaryOp\Greater::class => [ 70, 70, 70],
@@ -76,7 +79,7 @@ abstract class PrettyPrinterAbstract implements PrettyPrinter {
BinaryOp\BooleanAnd::class => [120, 121, 120],
BinaryOp\BooleanOr::class => [130, 131, 130],
BinaryOp\Coalesce::class => [140, 140, 141],
Expr\Ternary::class => [150, -1, -1],
Expr\Ternary::class => [150, 150, 150],
Expr\Assign::class => [160, -1, -1],
Expr\AssignRef::class => [160, -1, -1],
AssignOp\Plus::class => [160, -1, -1],
@@ -101,10 +104,20 @@ abstract class PrettyPrinterAbstract implements PrettyPrinter {
Expr\Include_::class => [220, -1, -1],
Expr\ArrowFunction::class => [230, -1, -1],
Expr\Throw_::class => [240, -1, -1],
Expr\Cast\Void_::class => [250, -1, -1],
];
/** @var int Current indentation level. */
protected int $indentLevel;
/** @var string String for single level of indentation */
private string $indent;
/** @var int Width in spaces to indent by. */
private int $indentWidth;
/** @var bool Whether to use tab indentation. */
private bool $useTabs;
/** @var int Width in spaces of one tab. */
private int $tabWidth = 4;
/** @var string Newline style. Does not include current indentation. */
protected string $newline;
/** @var string Newline including current indentation. */
@@ -169,12 +182,14 @@ abstract class PrettyPrinterAbstract implements PrettyPrinter {
* PHP version while specifying an older target (but the result will
* of course not be compatible with the older version in that case).
* * string $newline: The newline style to use. Should be "\n" (default) or "\r\n".
* * string $indent: The indentation to use. Should either be all spaces or a single
* tab. Defaults to four spaces (" ").
* * bool $shortArraySyntax: Whether to use [] instead of array() as the default array
* syntax, if the node does not specify a format. Defaults to whether
* the phpVersion support short array syntax.
*
* @param array{
* phpVersion?: PhpVersion, newline?: string, shortArraySyntax?: bool
* phpVersion?: PhpVersion, newline?: string, indent?: string, shortArraySyntax?: bool
* } $options Dictionary of formatting options
*/
public function __construct(array $options = []) {
@@ -189,6 +204,17 @@ abstract class PrettyPrinterAbstract implements PrettyPrinter {
$options['shortArraySyntax'] ?? $this->phpVersion->supportsShortArraySyntax();
$this->docStringEndToken =
$this->phpVersion->supportsFlexibleHeredoc() ? null : '_DOC_STRING_END_' . mt_rand();
$this->indent = $indent = $options['indent'] ?? ' ';
if ($indent === "\t") {
$this->useTabs = true;
$this->indentWidth = $this->tabWidth;
} elseif ($indent === \str_repeat(' ', \strlen($indent))) {
$this->useTabs = false;
$this->indentWidth = \strlen($indent);
} else {
throw new \LogicException('Option "indent" must either be all spaces or a single tab');
}
}
/**
@@ -207,24 +233,29 @@ abstract class PrettyPrinterAbstract implements PrettyPrinter {
*/
protected function setIndentLevel(int $level): void {
$this->indentLevel = $level;
$this->nl = $this->newline . \str_repeat(' ', $level);
if ($this->useTabs) {
$tabs = \intdiv($level, $this->tabWidth);
$spaces = $level % $this->tabWidth;
$this->nl = $this->newline . \str_repeat("\t", $tabs) . \str_repeat(' ', $spaces);
} else {
$this->nl = $this->newline . \str_repeat(' ', $level);
}
}
/**
* Increase indentation level.
*/
protected function indent(): void {
$this->indentLevel += 4;
$this->nl .= ' ';
$this->indentLevel += $this->indentWidth;
$this->nl .= $this->indent;
}
/**
* Decrease indentation level.
*/
protected function outdent(): void {
assert($this->indentLevel >= 4);
$this->indentLevel -= 4;
$this->nl = $this->newline . str_repeat(' ', $this->indentLevel);
assert($this->indentLevel >= $this->indentWidth);
$this->setIndentLevel($this->indentLevel - $this->indentWidth);
}
/**
@@ -536,7 +567,7 @@ abstract class PrettyPrinterAbstract implements PrettyPrinter {
$this->initializeModifierChangeMap();
$this->resetState();
$this->origTokens = new TokenStream($origTokens);
$this->origTokens = new TokenStream($origTokens, $this->tabWidth);
$this->preprocessNodes($stmts);
@@ -708,7 +739,7 @@ abstract class PrettyPrinterAbstract implements PrettyPrinter {
$result .= $extraLeft;
$origIndentLevel = $this->indentLevel;
$this->setIndentLevel($this->origTokens->getIndentationBefore($subStartPos) + $indentAdjustment);
$this->setIndentLevel(max($this->origTokens->getIndentationBefore($subStartPos) + $indentAdjustment, 0));
// If it's the same node that was previously in this position, it certainly doesn't
// need fixup. It's important to check this here, because our fixup checks are more
@@ -811,7 +842,7 @@ abstract class PrettyPrinterAbstract implements PrettyPrinter {
\assert($itemStartPos >= 0 && $itemEndPos >= 0 && $itemStartPos >= $pos);
$origIndentLevel = $this->indentLevel;
$lastElemIndentLevel = $this->origTokens->getIndentationBefore($itemStartPos) + $indentAdjustment;
$lastElemIndentLevel = max($this->origTokens->getIndentationBefore($itemStartPos) + $indentAdjustment, 0);
$this->setIndentLevel($lastElemIndentLevel);
$comments = $arrItem->getComments();
@@ -826,9 +857,8 @@ abstract class PrettyPrinterAbstract implements PrettyPrinter {
}
if ($skipRemovedNode) {
if ($isStmtList && ($this->origTokens->haveBracesInRange($pos, $itemStartPos) ||
$this->origTokens->haveTagInRange($pos, $itemStartPos))) {
// We'd remove the brace of a code block.
if ($isStmtList && $this->origTokens->haveTagInRange($pos, $itemStartPos)) {
// We'd remove an opening/closing PHP tag.
// TODO: Preserve formatting.
$this->setIndentLevel($origIndentLevel);
return null;
@@ -937,9 +967,8 @@ abstract class PrettyPrinterAbstract implements PrettyPrinter {
$pos, $itemStartPos, $indentAdjustment);
$skipRemovedNode = true;
} else {
if ($isStmtList && ($this->origTokens->haveBracesInRange($pos, $itemStartPos) ||
$this->origTokens->haveTagInRange($pos, $itemStartPos))) {
// We'd remove the brace of a code block.
if ($isStmtList && $this->origTokens->haveTagInRange($pos, $itemStartPos)) {
// We'd remove an opening/closing PHP tag.
// TODO: Preserve formatting.
return null;
}
@@ -1197,6 +1226,9 @@ abstract class PrettyPrinterAbstract implements PrettyPrinter {
. ($modifiers & Modifiers::PUBLIC ? 'public ' : '')
. ($modifiers & Modifiers::PROTECTED ? 'protected ' : '')
. ($modifiers & Modifiers::PRIVATE ? 'private ' : '')
. ($modifiers & Modifiers::PUBLIC_SET ? 'public(set) ' : '')
. ($modifiers & Modifiers::PROTECTED_SET ? 'protected(set) ' : '')
. ($modifiers & Modifiers::PRIVATE_SET ? 'private(set) ' : '')
. ($modifiers & Modifiers::STATIC ? 'static ' : '')
. ($modifiers & Modifiers::READONLY ? 'readonly ' : '');
}
@@ -1343,7 +1375,7 @@ abstract class PrettyPrinterAbstract implements PrettyPrinter {
BinaryOp\NotIdentical::class, BinaryOp\Spaceship::class, BinaryOp\BitwiseAnd::class,
BinaryOp\BitwiseXor::class, BinaryOp\BitwiseOr::class, BinaryOp\BooleanAnd::class,
BinaryOp\BooleanOr::class, BinaryOp\Coalesce::class, BinaryOp\LogicalAnd::class,
BinaryOp\LogicalXor::class, BinaryOp\LogicalOr::class,
BinaryOp\LogicalXor::class, BinaryOp\LogicalOr::class, BinaryOp\Pipe::class,
];
foreach ($binaryOps as $binaryOp) {
$this->fixupMap[$binaryOp] = [
@@ -1519,6 +1551,7 @@ abstract class PrettyPrinterAbstract implements PrettyPrinter {
Stmt\UseUse::class . '->uses' => ', ',
MatchArm::class . '->conds' => ', ',
AttributeGroup::class . '->attrs' => ', ',
PropertyHook::class . '->params' => ', ',
// statement lists
Expr\Closure::class . '->stmts' => "\n",
@@ -1540,6 +1573,7 @@ abstract class PrettyPrinterAbstract implements PrettyPrinter {
Stmt\Function_::class . '->stmts' => "\n",
Stmt\If_::class . '->stmts' => "\n",
Stmt\Namespace_::class . '->stmts' => "\n",
Stmt\Block::class . '->stmts' => "\n",
// Attribute groups
Stmt\Class_::class . '->attrGroups' => "\n",
@@ -1555,10 +1589,15 @@ abstract class PrettyPrinterAbstract implements PrettyPrinter {
Expr\Closure::class . '->attrGroups' => ' ',
Expr\ArrowFunction::class . '->attrGroups' => ' ',
Param::class . '->attrGroups' => ' ',
PropertyHook::class . '->attrGroups' => ' ',
Stmt\Switch_::class . '->cases' => "\n",
Stmt\TraitUse::class . '->adaptations' => "\n",
Stmt\TryCatch::class . '->stmts' => "\n",
Stmt\While_::class . '->stmts' => "\n",
PropertyHook::class . '->body' => "\n",
Stmt\Property::class . '->hooks' => "\n",
Param::class . '->hooks' => "\n",
// dummy for top-level context
'File->stmts' => "\n",
@@ -1598,6 +1637,7 @@ abstract class PrettyPrinterAbstract implements PrettyPrinter {
Stmt\Trait_::class . '->attrGroups' => [null, '', "\n"],
Expr\ArrowFunction::class . '->attrGroups' => [null, '', ' '],
Expr\Closure::class . '->attrGroups' => [null, '', ' '],
Stmt\Const_::class . '->attrGroups' => [null, '', "\n"],
PrintableNewAnonClassNode::class . '->attrGroups' => [\T_NEW, ' ', ''],
/* These cannot be empty to start with:
@@ -1642,6 +1682,7 @@ abstract class PrettyPrinterAbstract implements PrettyPrinter {
Stmt\Property::class . '->flags' => ['pModifiers', \T_VARIABLE],
PrintableNewAnonClassNode::class . '->flags' => ['pModifiers', \T_CLASS],
Param::class . '->flags' => ['pModifiers', \T_VARIABLE],
PropertyHook::class . '->flags' => ['pModifiers', \T_STRING],
Expr\Closure::class . '->static' => ['pStatic', \T_FUNCTION],
Expr\ArrowFunction::class . '->static' => ['pStatic', \T_FN],
//Stmt\TraitUseAdaptation\Alias::class . '->newModifier' => 0, // TODO

@@ -17,6 +17,14 @@ if (!\function_exists('PhpParser\defineCompatibilityTokens')) {
'T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG',
'T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG',
'T_READONLY',
// PHP 8.4
'T_PROPERTY_C',
'T_PUBLIC_SET',
'T_PROTECTED_SET',
'T_PRIVATE_SET',
// PHP 8.5
'T_PIPE',
'T_VOID_CAST',
];
// PHP-Parser might be used together with another library that also emulates some or all
@@ -26,6 +34,13 @@ if (!\function_exists('PhpParser\defineCompatibilityTokens')) {
foreach ($compatTokens as $token) {
if (\defined($token)) {
$tokenId = \constant($token);
if (!\is_int($tokenId)) {
throw new \Error(sprintf(
'Token %s has ID of type %s, should be int. ' .
'You may be using a library with broken token emulation',
$token, \gettype($tokenId)
));
}
$clashingToken = $usedTokenIds[$tokenId] ?? null;
if ($clashingToken !== null) {
throw new \Error(sprintf(

@@ -1,241 +1,271 @@
parameters:
ignoreErrors:
-
message: "#^Method PhpParser\\\\Builder\\\\ClassConst\\:\\:__construct\\(\\) has parameter \\$value with no value type specified in iterable type array\\.$#"
message: '#^Method PhpParser\\Builder\\ClassConst\:\:__construct\(\) has parameter \$value with no value type specified in iterable type array\.$#'
identifier: missingType.iterableValue
count: 1
path: lib/PhpParser/Builder/ClassConst.php
-
message: "#^Method PhpParser\\\\Builder\\\\ClassConst\\:\\:addConst\\(\\) has parameter \\$value with no value type specified in iterable type array\\.$#"
message: '#^Method PhpParser\\Builder\\ClassConst\:\:addConst\(\) has parameter \$value with no value type specified in iterable type array\.$#'
identifier: missingType.iterableValue
count: 1
path: lib/PhpParser/Builder/ClassConst.php
-
message: "#^Method PhpParser\\\\BuilderFactory\\:\\:args\\(\\) has parameter \\$args with no value type specified in iterable type array\\.$#"
message: '#^Method PhpParser\\BuilderFactory\:\:args\(\) has parameter \$args with no value type specified in iterable type array\.$#'
identifier: missingType.iterableValue
count: 1
path: lib/PhpParser/BuilderFactory.php
-
message: "#^Method PhpParser\\\\BuilderFactory\\:\\:attribute\\(\\) has parameter \\$args with no value type specified in iterable type array\\.$#"
message: '#^Method PhpParser\\BuilderFactory\:\:attribute\(\) has parameter \$args with no value type specified in iterable type array\.$#'
identifier: missingType.iterableValue
count: 1
path: lib/PhpParser/BuilderFactory.php
-
message: "#^Method PhpParser\\\\BuilderFactory\\:\\:classConst\\(\\) has parameter \\$value with no value type specified in iterable type array\\.$#"
message: '#^Method PhpParser\\BuilderFactory\:\:classConst\(\) has parameter \$value with no value type specified in iterable type array\.$#'
identifier: missingType.iterableValue
count: 1
path: lib/PhpParser/BuilderFactory.php
-
message: "#^Method PhpParser\\\\BuilderFactory\\:\\:funcCall\\(\\) has parameter \\$args with no value type specified in iterable type array\\.$#"
message: '#^Method PhpParser\\BuilderFactory\:\:funcCall\(\) has parameter \$args with no value type specified in iterable type array\.$#'
identifier: missingType.iterableValue
count: 1
path: lib/PhpParser/BuilderFactory.php
-
message: "#^Method PhpParser\\\\BuilderFactory\\:\\:methodCall\\(\\) has parameter \\$args with no value type specified in iterable type array\\.$#"
message: '#^Method PhpParser\\BuilderFactory\:\:methodCall\(\) has parameter \$args with no value type specified in iterable type array\.$#'
identifier: missingType.iterableValue
count: 1
path: lib/PhpParser/BuilderFactory.php
-
message: "#^Method PhpParser\\\\BuilderFactory\\:\\:new\\(\\) has parameter \\$args with no value type specified in iterable type array\\.$#"
message: '#^Method PhpParser\\BuilderFactory\:\:new\(\) has parameter \$args with no value type specified in iterable type array\.$#'
identifier: missingType.iterableValue
count: 1
path: lib/PhpParser/BuilderFactory.php
-
message: "#^Method PhpParser\\\\BuilderFactory\\:\\:staticCall\\(\\) has parameter \\$args with no value type specified in iterable type array\\.$#"
message: '#^Method PhpParser\\BuilderFactory\:\:staticCall\(\) has parameter \$args with no value type specified in iterable type array\.$#'
identifier: missingType.iterableValue
count: 1
path: lib/PhpParser/BuilderFactory.php
-
message: "#^Method PhpParser\\\\BuilderFactory\\:\\:val\\(\\) has parameter \\$value with no value type specified in iterable type array\\.$#"
message: '#^Method PhpParser\\BuilderFactory\:\:val\(\) has parameter \$value with no value type specified in iterable type array\.$#'
identifier: missingType.iterableValue
count: 1
path: lib/PhpParser/BuilderFactory.php
-
message: "#^Method PhpParser\\\\BuilderHelpers\\:\\:normalizeValue\\(\\) has parameter \\$value with no value type specified in iterable type array\\.$#"
message: '#^Method PhpParser\\BuilderHelpers\:\:normalizeValue\(\) has parameter \$value with no value type specified in iterable type array\.$#'
identifier: missingType.iterableValue
count: 1
path: lib/PhpParser/BuilderHelpers.php
-
message: "#^Method PhpParser\\\\ConstExprEvaluator\\:\\:evaluateArray\\(\\) return type has no value type specified in iterable type array\\.$#"
message: '#^Method PhpParser\\ConstExprEvaluator\:\:evaluateArray\(\) return type has no value type specified in iterable type array\.$#'
identifier: missingType.iterableValue
count: 1
path: lib/PhpParser/ConstExprEvaluator.php
-
message: "#^Unary operation \"~\" on mixed results in an error\\.$#"
count: 1
path: lib/PhpParser/ConstExprEvaluator.php
-
message: "#^Method PhpParser\\\\JsonDecoder\\:\\:decodeArray\\(\\) has parameter \\$array with no value type specified in iterable type array\\.$#"
message: '#^Method PhpParser\\JsonDecoder\:\:decodeArray\(\) has parameter \$array with no value type specified in iterable type array\.$#'
identifier: missingType.iterableValue
count: 1
path: lib/PhpParser/JsonDecoder.php
-
message: "#^Method PhpParser\\\\JsonDecoder\\:\\:decodeArray\\(\\) return type has no value type specified in iterable type array\\.$#"
message: '#^Method PhpParser\\JsonDecoder\:\:decodeArray\(\) return type has no value type specified in iterable type array\.$#'
identifier: missingType.iterableValue
count: 1
path: lib/PhpParser/JsonDecoder.php
-
message: "#^Method PhpParser\\\\JsonDecoder\\:\\:decodeComment\\(\\) has parameter \\$value with no value type specified in iterable type array\\.$#"
message: '#^Method PhpParser\\JsonDecoder\:\:decodeComment\(\) has parameter \$value with no value type specified in iterable type array\.$#'
identifier: missingType.iterableValue
count: 1
path: lib/PhpParser/JsonDecoder.php
-
message: "#^Method PhpParser\\\\JsonDecoder\\:\\:decodeNode\\(\\) has parameter \\$value with no value type specified in iterable type array\\.$#"
message: '#^Method PhpParser\\JsonDecoder\:\:decodeNode\(\) has parameter \$value with no value type specified in iterable type array\.$#'
identifier: missingType.iterableValue
count: 1
path: lib/PhpParser/JsonDecoder.php
-
message: "#^Call to function assert\\(\\) with false will always evaluate to false\\.$#"
message: '#^Call to function assert\(\) with false will always evaluate to false\.$#'
identifier: function.impossibleType
count: 1
path: lib/PhpParser/Lexer/Emulative.php
-
message: "#^Method PhpParser\\\\NodeDumper\\:\\:__construct\\(\\) has parameter \\$options with no value type specified in iterable type array\\.$#"
message: '#^Constant T_PIPE not found\.$#'
identifier: constant.notFound
count: 2
path: lib/PhpParser/Lexer/TokenEmulator/PipeOperatorEmulator.php
-
message: '#^Constant T_VOID_CAST not found\.$#'
identifier: constant.notFound
count: 2
path: lib/PhpParser/Lexer/TokenEmulator/VoidCastEmulator.php
-
message: '#^If condition is always false\.$#'
identifier: if.alwaysFalse
count: 1
path: lib/PhpParser/Node/Expr/ArrayItem.php
-
message: '#^If condition is always false\.$#'
identifier: if.alwaysFalse
count: 1
path: lib/PhpParser/Node/Expr/ClosureUse.php
-
message: '#^If condition is always false\.$#'
identifier: if.alwaysFalse
count: 1
path: lib/PhpParser/Node/Scalar/DNumber.php
-
message: '#^If condition is always false\.$#'
identifier: if.alwaysFalse
count: 1
path: lib/PhpParser/Node/Scalar/Encapsed.php
-
message: '#^If condition is always false\.$#'
identifier: if.alwaysFalse
count: 1
path: lib/PhpParser/Node/Scalar/EncapsedStringPart.php
-
message: '#^If condition is always false\.$#'
identifier: if.alwaysFalse
count: 1
path: lib/PhpParser/Node/Scalar/LNumber.php
-
message: '#^If condition is always false\.$#'
identifier: if.alwaysFalse
count: 1
path: lib/PhpParser/Node/Stmt/DeclareDeclare.php
-
message: '#^If condition is always false\.$#'
identifier: if.alwaysFalse
count: 1
path: lib/PhpParser/Node/Stmt/PropertyProperty.php
-
message: '#^If condition is always false\.$#'
identifier: if.alwaysFalse
count: 1
path: lib/PhpParser/Node/Stmt/StaticVar.php
-
message: '#^If condition is always false\.$#'
identifier: if.alwaysFalse
count: 1
path: lib/PhpParser/Node/Stmt/UseUse.php
-
message: '#^Method PhpParser\\NodeDumper\:\:__construct\(\) has parameter \$options with no value type specified in iterable type array\.$#'
identifier: missingType.iterableValue
count: 1
path: lib/PhpParser/NodeDumper.php
-
message: "#^Method PhpParser\\\\NodeDumper\\:\\:dump\\(\\) has parameter \\$node with no value type specified in iterable type array\\.$#"
message: '#^Method PhpParser\\NodeDumper\:\:dump\(\) has parameter \$node with no value type specified in iterable type array\.$#'
identifier: missingType.iterableValue
count: 1
path: lib/PhpParser/NodeDumper.php
-
message: "#^Method PhpParser\\\\NodeDumper\\:\\:dumpRecursive\\(\\) has parameter \\$node with no value type specified in iterable type array\\.$#"
count: 1
path: lib/PhpParser/NodeDumper.php
-
message: "#^Method PhpParser\\\\NodeTraverser\\:\\:traverseArray\\(\\) has parameter \\$nodes with no value type specified in iterable type array\\.$#"
message: '#^Method PhpParser\\NodeTraverser\:\:traverseArray\(\) has parameter \$nodes with no value type specified in iterable type array\.$#'
identifier: missingType.iterableValue
count: 1
path: lib/PhpParser/NodeTraverser.php
-
message: "#^Method PhpParser\\\\NodeTraverser\\:\\:traverseArray\\(\\) return type has no value type specified in iterable type array\\.$#"
message: '#^Method PhpParser\\NodeTraverser\:\:traverseArray\(\) return type has no value type specified in iterable type array\.$#'
identifier: missingType.iterableValue
count: 1
path: lib/PhpParser/NodeTraverser.php
-
message: "#^Access to an undefined property PhpParser\\\\Node\\:\\:\\$attrGroups\\.$#"
message: '#^Access to an undefined property PhpParser\\Node\:\:\$attrGroups\.$#'
identifier: property.notFound
count: 1
path: lib/PhpParser/NodeVisitor/NameResolver.php
-
message: "#^Access to an undefined property PhpParser\\\\Node\\:\\:\\$name\\.$#"
message: '#^Access to an undefined property PhpParser\\Node\:\:\$name\.$#'
identifier: property.notFound
count: 1
path: lib/PhpParser/NodeVisitor/NameResolver.php
-
message: "#^Access to an undefined property PhpParser\\\\Node\\:\\:\\$namespacedName\\.$#"
message: '#^Access to an undefined property PhpParser\\Node\:\:\$namespacedName\.$#'
identifier: property.notFound
count: 1
path: lib/PhpParser/NodeVisitor/NameResolver.php
-
message: "#^Method PhpParser\\\\NodeVisitor\\\\NodeConnectingVisitor\\:\\:beforeTraverse\\(\\) should return array\\<PhpParser\\\\Node\\>\\|null but return statement is missing\\.$#"
message: '#^Method PhpParser\\NodeVisitor\\NodeConnectingVisitor\:\:beforeTraverse\(\) should return array\<PhpParser\\Node\>\|null but return statement is missing\.$#'
identifier: return.missing
count: 1
path: lib/PhpParser/NodeVisitor/NodeConnectingVisitor.php
-
message: "#^Method PhpParser\\\\NodeVisitor\\\\NodeConnectingVisitor\\:\\:enterNode\\(\\) should return array\\<PhpParser\\\\Node\\>\\|int\\|PhpParser\\\\Node\\|null but return statement is missing\\.$#"
message: '#^Method PhpParser\\NodeVisitor\\NodeConnectingVisitor\:\:enterNode\(\) should return array\<PhpParser\\Node\>\|int\|PhpParser\\Node\|null but return statement is missing\.$#'
identifier: return.missing
count: 1
path: lib/PhpParser/NodeVisitor/NodeConnectingVisitor.php
-
message: "#^Method PhpParser\\\\NodeVisitor\\\\NodeConnectingVisitor\\:\\:leaveNode\\(\\) should return array\\<PhpParser\\\\Node\\>\\|int\\|PhpParser\\\\Node\\|null but return statement is missing\\.$#"
message: '#^Method PhpParser\\NodeVisitor\\NodeConnectingVisitor\:\:leaveNode\(\) should return array\<PhpParser\\Node\>\|int\|PhpParser\\Node\|null but return statement is missing\.$#'
identifier: return.missing
count: 1
path: lib/PhpParser/NodeVisitor/NodeConnectingVisitor.php
-
message: "#^Method PhpParser\\\\NodeVisitor\\\\ParentConnectingVisitor\\:\\:beforeTraverse\\(\\) should return array\\<PhpParser\\\\Node\\>\\|null but return statement is missing\\.$#"
message: '#^Method PhpParser\\NodeVisitor\\ParentConnectingVisitor\:\:beforeTraverse\(\) should return array\<PhpParser\\Node\>\|null but return statement is missing\.$#'
identifier: return.missing
count: 1
path: lib/PhpParser/NodeVisitor/ParentConnectingVisitor.php
-
message: "#^Method PhpParser\\\\NodeVisitor\\\\ParentConnectingVisitor\\:\\:enterNode\\(\\) should return array\\<PhpParser\\\\Node\\>\\|int\\|PhpParser\\\\Node\\|null but return statement is missing\\.$#"
message: '#^Method PhpParser\\NodeVisitor\\ParentConnectingVisitor\:\:enterNode\(\) should return array\<PhpParser\\Node\>\|int\|PhpParser\\Node\|null but return statement is missing\.$#'
identifier: return.missing
count: 1
path: lib/PhpParser/NodeVisitor/ParentConnectingVisitor.php
-
message: "#^Method PhpParser\\\\NodeVisitor\\\\ParentConnectingVisitor\\:\\:leaveNode\\(\\) should return array\\<PhpParser\\\\Node\\>\\|int\\|PhpParser\\\\Node\\|null but return statement is missing\\.$#"
message: '#^Method PhpParser\\NodeVisitor\\ParentConnectingVisitor\:\:leaveNode\(\) should return array\<PhpParser\\Node\>\|int\|PhpParser\\Node\|null but return statement is missing\.$#'
identifier: return.missing
count: 1
path: lib/PhpParser/NodeVisitor/ParentConnectingVisitor.php
-
message: "#^Access to undefined constant static\\(PhpParser\\\\ParserAbstract\\)\\:\\:T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG\\.$#"
message: '#^Access to undefined constant static\(PhpParser\\ParserAbstract\)\:\:T_ECHO\.$#'
identifier: classConstant.notFound
count: 1
path: lib/PhpParser/ParserAbstract.php
-
message: "#^Access to undefined constant static\\(PhpParser\\\\ParserAbstract\\)\\:\\:T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG\\.$#"
message: '#^Unary operation "\+" on string results in an error\.$#'
identifier: unaryOp.invalid
count: 1
path: lib/PhpParser/ParserAbstract.php
-
message: "#^Access to undefined constant static\\(PhpParser\\\\ParserAbstract\\)\\:\\:T_ATTRIBUTE\\.$#"
count: 1
path: lib/PhpParser/ParserAbstract.php
-
message: "#^Access to undefined constant static\\(PhpParser\\\\ParserAbstract\\)\\:\\:T_COALESCE_EQUAL\\.$#"
count: 1
path: lib/PhpParser/ParserAbstract.php
-
message: "#^Access to undefined constant static\\(PhpParser\\\\ParserAbstract\\)\\:\\:T_ECHO\\.$#"
count: 1
path: lib/PhpParser/ParserAbstract.php
-
message: "#^Access to undefined constant static\\(PhpParser\\\\ParserAbstract\\)\\:\\:T_ENUM\\.$#"
count: 1
path: lib/PhpParser/ParserAbstract.php
-
message: "#^Access to undefined constant static\\(PhpParser\\\\ParserAbstract\\)\\:\\:T_FN\\.$#"
count: 1
path: lib/PhpParser/ParserAbstract.php
-
message: "#^Access to undefined constant static\\(PhpParser\\\\ParserAbstract\\)\\:\\:T_MATCH\\.$#"
count: 1
path: lib/PhpParser/ParserAbstract.php
-
message: "#^Access to undefined constant static\\(PhpParser\\\\ParserAbstract\\)\\:\\:T_NAME_FULLY_QUALIFIED\\.$#"
count: 1
path: lib/PhpParser/ParserAbstract.php
-
message: "#^Access to undefined constant static\\(PhpParser\\\\ParserAbstract\\)\\:\\:T_NAME_QUALIFIED\\.$#"
count: 1
path: lib/PhpParser/ParserAbstract.php
-
message: "#^Access to undefined constant static\\(PhpParser\\\\ParserAbstract\\)\\:\\:T_NAME_RELATIVE\\.$#"
count: 1
path: lib/PhpParser/ParserAbstract.php
-
message: "#^Access to undefined constant static\\(PhpParser\\\\ParserAbstract\\)\\:\\:T_NULLSAFE_OBJECT_OPERATOR\\.$#"
count: 1
path: lib/PhpParser/ParserAbstract.php
-
message: "#^Access to undefined constant static\\(PhpParser\\\\ParserAbstract\\)\\:\\:T_PAAMAYIM_NEKUDOTAYIM\\.$#"
count: 1
path: lib/PhpParser/ParserAbstract.php
-
message: "#^Access to undefined constant static\\(PhpParser\\\\ParserAbstract\\)\\:\\:T_READONLY\\.$#"
count: 1
path: lib/PhpParser/ParserAbstract.php
-
message: "#^Unary operation \"\\+\" on string results in an error\\.$#"
count: 1
path: lib/PhpParser/ParserAbstract.php
-
message: "#^Variable \\$action might not be defined\\.$#"
message: '#^Variable \$action might not be defined\.$#'
identifier: variable.undefined
count: 1
path: lib/PhpParser/ParserAbstract.php

@@ -4,6 +4,7 @@
xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
backupGlobals="false"
colors="true"
convertDeprecationsToExceptions="true"
beStrictAboutTestsThatDoNotTestAnything="false"
bootstrap="./test/bootstrap.php">
<testsuites>
@@ -12,9 +13,9 @@
</testsuite>
</testsuites>
<filter>
<whitelist>
<coverage>
<include>
<directory suffix=".php">./lib/PhpParser/</directory>
</whitelist>
</filter>
</include>
</coverage>
</phpunit>

@@ -20,7 +20,7 @@ class ClassConstTest extends \PHPUnit\Framework\TestCase {
return new ClassConst($name, $value);
}
public function testModifiers() {
public function testModifiers(): void {
$node = $this->createClassConstBuilder("TEST", 1)
->makePrivate()
->getNode()
@@ -82,7 +82,7 @@ class ClassConstTest extends \PHPUnit\Framework\TestCase {
);
}
public function testDocComment() {
public function testDocComment(): void {
$node = $this->createClassConstBuilder('TEST', 1)
->setDocComment('/** Test */')
->makePublic()
@@ -102,7 +102,7 @@ class ClassConstTest extends \PHPUnit\Framework\TestCase {
);
}
public function testAddConst() {
public function testAddConst(): void {
$node = $this->createClassConstBuilder('FIRST_TEST', 1)
->addConst("SECOND_TEST", 2)
->getNode();
@@ -118,7 +118,7 @@ class ClassConstTest extends \PHPUnit\Framework\TestCase {
);
}
public function testAddAttribute() {
public function testAddAttribute(): void {
$attribute = new Attribute(
new Name('Attr'),
[new Arg(new Int_(1), false, false, [], new Identifier('name'))]
@@ -142,7 +142,7 @@ class ClassConstTest extends \PHPUnit\Framework\TestCase {
);
}
public function testType() {
public function testType(): void {
$node = $this->createClassConstBuilder('TYPE', 1)
->setType('int')
->getNode();
@@ -157,7 +157,7 @@ class ClassConstTest extends \PHPUnit\Framework\TestCase {
/**
* @dataProvider provideTestDefaultValues
*/
public function testValues($value, $expectedValueNode) {
public function testValues($value, $expectedValueNode): void {
$node = $this->createClassConstBuilder('TEST', $value)
->getNode()
;
@@ -165,7 +165,7 @@ class ClassConstTest extends \PHPUnit\Framework\TestCase {
$this->assertEquals($expectedValueNode, $node->consts[0]->value);
}
public function provideTestDefaultValues() {
public static function provideTestDefaultValues() {
return [
[
null,

@@ -18,7 +18,7 @@ class ClassTest extends \PHPUnit\Framework\TestCase {
return new Class_($class);
}
public function testExtendsImplements() {
public function testExtendsImplements(): void {
$node = $this->createClassBuilder('SomeLogger')
->extend('BaseLogger')
->implement('Namespaced\Logger', new Name('SomeInterface'))
@@ -40,7 +40,7 @@ class ClassTest extends \PHPUnit\Framework\TestCase {
);
}
public function testAbstract() {
public function testAbstract(): void {
$node = $this->createClassBuilder('Test')
->makeAbstract()
->getNode()
@@ -54,7 +54,7 @@ class ClassTest extends \PHPUnit\Framework\TestCase {
);
}
public function testFinal() {
public function testFinal(): void {
$node = $this->createClassBuilder('Test')
->makeFinal()
->getNode()
@@ -68,7 +68,7 @@ class ClassTest extends \PHPUnit\Framework\TestCase {
);
}
public function testReadonly() {
public function testReadonly(): void {
$node = $this->createClassBuilder('Test')
->makeReadonly()
->getNode()
@@ -82,7 +82,7 @@ class ClassTest extends \PHPUnit\Framework\TestCase {
);
}
public function testStatementOrder() {
public function testStatementOrder(): void {
$method = new Stmt\ClassMethod('testMethod');
$property = new Stmt\Property(
Modifiers::PUBLIC,
@@ -108,7 +108,7 @@ class ClassTest extends \PHPUnit\Framework\TestCase {
);
}
public function testDocComment() {
public function testDocComment(): void {
$docComment = <<<'DOC'
/**
* Test
@@ -141,7 +141,7 @@ DOC;
);
}
public function testAddAttribute() {
public function testAddAttribute(): void {
$attribute = new Attribute(
new Name('Attr'),
[new Arg(new Int_(1), false, false, [], new Identifier('name'))]
@@ -162,7 +162,7 @@ DOC;
);
}
public function testInvalidStmtError() {
public function testInvalidStmtError(): void {
$this->expectException(\LogicException::class);
$this->expectExceptionMessage('Unexpected node of type "Stmt_Echo"');
$this->createClassBuilder('Test')
@@ -170,21 +170,21 @@ DOC;
;
}
public function testInvalidDocComment() {
public function testInvalidDocComment(): void {
$this->expectException(\LogicException::class);
$this->expectExceptionMessage('Doc comment must be a string or an instance of PhpParser\Comment\Doc');
$this->createClassBuilder('Test')
->setDocComment(new Comment('Test'));
}
public function testEmptyName() {
public function testEmptyName(): void {
$this->expectException(\LogicException::class);
$this->expectExceptionMessage('Name cannot be empty');
$this->createClassBuilder('Test')
->extend('');
}
public function testInvalidName() {
public function testInvalidName(): void {
$this->expectException(\LogicException::class);
$this->expectExceptionMessage('Name must be a string or an instance of Node\Name');
$this->createClassBuilder('Test')

@@ -17,7 +17,7 @@ class EnumCaseTest extends \PHPUnit\Framework\TestCase {
return new EnumCase($name);
}
public function testDocComment() {
public function testDocComment(): void {
$node = $this->createEnumCaseBuilder('TEST')
->setDocComment('/** Test */')
->getNode();
@@ -35,7 +35,7 @@ class EnumCaseTest extends \PHPUnit\Framework\TestCase {
);
}
public function testAddAttribute() {
public function testAddAttribute(): void {
$attribute = new Attribute(
new Name('Attr'),
[new Arg(new Int_(1), false, false, [], new Identifier('name'))]
@@ -59,7 +59,7 @@ class EnumCaseTest extends \PHPUnit\Framework\TestCase {
/**
* @dataProvider provideTestDefaultValues
*/
public function testValues($value, $expectedValueNode) {
public function testValues($value, $expectedValueNode): void {
$node = $this->createEnumCaseBuilder('TEST')
->setValue($value)
->getNode()
@@ -68,7 +68,7 @@ class EnumCaseTest extends \PHPUnit\Framework\TestCase {
$this->assertEquals($expectedValueNode, $node->expr);
}
public function provideTestDefaultValues() {
public static function provideTestDefaultValues() {
return [
[
31415,

@@ -17,7 +17,7 @@ class EnumTest extends \PHPUnit\Framework\TestCase {
return new Enum_($class);
}
public function testImplements() {
public function testImplements(): void {
$node = $this->createEnumBuilder('SomeEnum')
->implement('Namespaced\SomeInterface', new Name('OtherInterface'))
->getNode()
@@ -34,7 +34,7 @@ class EnumTest extends \PHPUnit\Framework\TestCase {
);
}
public function testSetScalarType() {
public function testSetScalarType(): void {
$node = $this->createEnumBuilder('Test')
->setScalarType('int')
->getNode()
@@ -48,7 +48,7 @@ class EnumTest extends \PHPUnit\Framework\TestCase {
);
}
public function testStatementOrder() {
public function testStatementOrder(): void {
$method = new Stmt\ClassMethod('testMethod');
$enumCase = new Stmt\EnumCase(
'TEST_ENUM_CASE'
@@ -73,7 +73,7 @@ class EnumTest extends \PHPUnit\Framework\TestCase {
);
}
public function testDocComment() {
public function testDocComment(): void {
$docComment = <<<'DOC'
/**
* Test
@@ -106,7 +106,7 @@ DOC;
);
}
public function testAddAttribute() {
public function testAddAttribute(): void {
$attribute = new Attribute(
new Name('Attr'),
[new Arg(new Int_(1), false, false, [], new Identifier('name'))]
@@ -127,7 +127,7 @@ DOC;
);
}
public function testInvalidStmtError() {
public function testInvalidStmtError(): void {
$this->expectException(\LogicException::class);
$this->expectExceptionMessage('Unexpected node of type "PropertyItem"');
$this->createEnumBuilder('Test')
@@ -135,21 +135,21 @@ DOC;
;
}
public function testInvalidDocComment() {
public function testInvalidDocComment(): void {
$this->expectException(\LogicException::class);
$this->expectExceptionMessage('Doc comment must be a string or an instance of PhpParser\Comment\Doc');
$this->createEnumBuilder('Test')
->setDocComment(new Comment('Test'));
}
public function testEmptyName() {
public function testEmptyName(): void {
$this->expectException(\LogicException::class);
$this->expectExceptionMessage('Name cannot be empty');
$this->createEnumBuilder('Test')
->implement('');
}
public function testInvalidName() {
public function testInvalidName(): void {
$this->expectException(\LogicException::class);
$this->expectExceptionMessage('Name must be a string or an instance of Node\Name');
$this->createEnumBuilder('Test')

@@ -20,7 +20,7 @@ class FunctionTest extends \PHPUnit\Framework\TestCase {
return new Function_($name);
}
public function testReturnByRef() {
public function testReturnByRef(): void {
$node = $this->createFunctionBuilder('test')
->makeReturnByRef()
->getNode()
@@ -34,7 +34,7 @@ class FunctionTest extends \PHPUnit\Framework\TestCase {
);
}
public function testParams() {
public function testParams(): void {
$param1 = new Node\Param(new Variable('test1'));
$param2 = new Node\Param(new Variable('test2'));
$param3 = new Node\Param(new Variable('test3'));
@@ -53,7 +53,7 @@ class FunctionTest extends \PHPUnit\Framework\TestCase {
);
}
public function testStmts() {
public function testStmts(): void {
$stmt1 = new Print_(new String_('test1'));
$stmt2 = new Print_(new String_('test2'));
$stmt3 = new Print_(new String_('test3'));
@@ -76,7 +76,7 @@ class FunctionTest extends \PHPUnit\Framework\TestCase {
);
}
public function testDocComment() {
public function testDocComment(): void {
$node = $this->createFunctionBuilder('test')
->setDocComment('/** Test */')
->getNode();
@@ -86,7 +86,7 @@ class FunctionTest extends \PHPUnit\Framework\TestCase {
]), $node);
}
public function testAddAttribute() {
public function testAddAttribute(): void {
$attribute = new Attribute(
new Name('Attr'),
[new Arg(new Int_(1), false, false, [], new Identifier('name'))]
@@ -102,7 +102,7 @@ class FunctionTest extends \PHPUnit\Framework\TestCase {
], []), $node);
}
public function testReturnType() {
public function testReturnType(): void {
$node = $this->createFunctionBuilder('test')
->setReturnType('void')
->getNode();
@@ -112,13 +112,13 @@ class FunctionTest extends \PHPUnit\Framework\TestCase {
], []), $node);
}
public function testInvalidNullableVoidType() {
public function testInvalidNullableVoidType(): void {
$this->expectException(\LogicException::class);
$this->expectExceptionMessage('void type cannot be nullable');
$this->createFunctionBuilder('test')->setReturnType('?void');
}
public function testInvalidParamError() {
public function testInvalidParamError(): void {
$this->expectException(\LogicException::class);
$this->expectExceptionMessage('Expected parameter node, got "Name"');
$this->createFunctionBuilder('test')
@@ -126,7 +126,7 @@ class FunctionTest extends \PHPUnit\Framework\TestCase {
;
}
public function testAddNonStmt() {
public function testAddNonStmt(): void {
$this->expectException(\LogicException::class);
$this->expectExceptionMessage('Expected statement or expression node');
$this->createFunctionBuilder('test')

@@ -23,13 +23,13 @@ class InterfaceTest extends \PHPUnit\Framework\TestCase {
return $pp->prettyPrint([$node]);
}
public function testEmpty() {
public function testEmpty(): void {
$contract = $this->createInterfaceBuilder()->getNode();
$this->assertInstanceOf(Stmt\Interface_::class, $contract);
$this->assertEquals(new Node\Identifier('Contract'), $contract->name);
}
public function testExtending() {
public function testExtending(): void {
$contract = $this->createInterfaceBuilder()
->extend('Space\Root1', 'Root2')->getNode();
$this->assertEquals(
@@ -42,13 +42,13 @@ class InterfaceTest extends \PHPUnit\Framework\TestCase {
);
}
public function testAddMethod() {
public function testAddMethod(): void {
$method = new Stmt\ClassMethod('doSomething');
$contract = $this->createInterfaceBuilder()->addStmt($method)->getNode();
$this->assertSame([$method], $contract->stmts);
}
public function testAddConst() {
public function testAddConst(): void {
$const = new Stmt\ClassConst([
new Node\Const_('SPEED_OF_LIGHT', new Float_(299792458.0))
]);
@@ -56,7 +56,7 @@ class InterfaceTest extends \PHPUnit\Framework\TestCase {
$this->assertSame(299792458.0, $contract->stmts[0]->consts[0]->value->value);
}
public function testOrder() {
public function testOrder(): void {
$const = new Stmt\ClassConst([
new Node\Const_('SPEED_OF_LIGHT', new Float_(299792458))
]);
@@ -71,7 +71,7 @@ class InterfaceTest extends \PHPUnit\Framework\TestCase {
$this->assertInstanceOf(Stmt\ClassMethod::class, $contract->stmts[1]);
}
public function testDocComment() {
public function testDocComment(): void {
$node = $this->createInterfaceBuilder()
->setDocComment('/** Test */')
->getNode();
@@ -81,7 +81,7 @@ class InterfaceTest extends \PHPUnit\Framework\TestCase {
]), $node);
}
public function testAddAttribute() {
public function testAddAttribute(): void {
$attribute = new Attribute(
new Name('Attr'),
[new Arg(new Int_(1), false, false, [], new Identifier('name'))]
@@ -97,13 +97,13 @@ class InterfaceTest extends \PHPUnit\Framework\TestCase {
], []), $node);
}
public function testInvalidStmtError() {
public function testInvalidStmtError(): void {
$this->expectException(\LogicException::class);
$this->expectExceptionMessage('Unexpected node of type "PropertyItem"');
$this->createInterfaceBuilder()->addStmt(new Node\PropertyItem('invalid'));
}
public function testFullFunctional() {
public function testFullFunctional(): void {
$const = new Stmt\ClassConst([
new Node\Const_('SPEED_OF_LIGHT', new Float_(299792458))
]);

@@ -21,7 +21,7 @@ class MethodTest extends \PHPUnit\Framework\TestCase {
return new Method($name);
}
public function testModifiers() {
public function testModifiers(): void {
$node = $this->createMethodBuilder('test')
->makePublic()
->makeAbstract()
@@ -63,7 +63,7 @@ class MethodTest extends \PHPUnit\Framework\TestCase {
);
}
public function testReturnByRef() {
public function testReturnByRef(): void {
$node = $this->createMethodBuilder('test')
->makeReturnByRef()
->getNode()
@@ -77,7 +77,7 @@ class MethodTest extends \PHPUnit\Framework\TestCase {
);
}
public function testParams() {
public function testParams(): void {
$param1 = new Node\Param(new Variable('test1'));
$param2 = new Node\Param(new Variable('test2'));
$param3 = new Node\Param(new Variable('test3'));
@@ -96,7 +96,7 @@ class MethodTest extends \PHPUnit\Framework\TestCase {
);
}
public function testStmts() {
public function testStmts(): void {
$stmt1 = new Print_(new String_('test1'));
$stmt2 = new Print_(new String_('test2'));
$stmt3 = new Print_(new String_('test3'));
@@ -118,7 +118,7 @@ class MethodTest extends \PHPUnit\Framework\TestCase {
$node
);
}
public function testDocComment() {
public function testDocComment(): void {
$node = $this->createMethodBuilder('test')
->setDocComment('/** Test */')
->getNode();
@@ -128,7 +128,7 @@ class MethodTest extends \PHPUnit\Framework\TestCase {
]), $node);
}
public function testAddAttribute() {
public function testAddAttribute(): void {
$attribute = new Attribute(
new Name('Attr'),
[new Arg(new Int_(1), false, false, [], new Identifier('name'))]
@@ -144,7 +144,7 @@ class MethodTest extends \PHPUnit\Framework\TestCase {
], []), $node);
}
public function testReturnType() {
public function testReturnType(): void {
$node = $this->createMethodBuilder('test')
->setReturnType('bool')
->getNode();
@@ -153,7 +153,7 @@ class MethodTest extends \PHPUnit\Framework\TestCase {
], []), $node);
}
public function testAddStmtToAbstractMethodError() {
public function testAddStmtToAbstractMethodError(): void {
$this->expectException(\LogicException::class);
$this->expectExceptionMessage('Cannot add statements to an abstract method');
$this->createMethodBuilder('test')
@@ -162,7 +162,7 @@ class MethodTest extends \PHPUnit\Framework\TestCase {
;
}
public function testMakeMethodWithStmtsAbstractError() {
public function testMakeMethodWithStmtsAbstractError(): void {
$this->expectException(\LogicException::class);
$this->expectExceptionMessage('Cannot make method with statements abstract');
$this->createMethodBuilder('test')
@@ -171,7 +171,7 @@ class MethodTest extends \PHPUnit\Framework\TestCase {
;
}
public function testInvalidParamError() {
public function testInvalidParamError(): void {
$this->expectException(\LogicException::class);
$this->expectExceptionMessage('Expected parameter node, got "Name"');
$this->createMethodBuilder('test')

@@ -11,7 +11,7 @@ class NamespaceTest extends \PHPUnit\Framework\TestCase {
return new Namespace_($fqn);
}
public function testCreation() {
public function testCreation(): void {
$stmt1 = new Stmt\Class_('SomeClass');
$stmt2 = new Stmt\Interface_('SomeInterface');
$stmt3 = new Stmt\Function_('someFunction');

@@ -21,7 +21,7 @@ class ParamTest extends \PHPUnit\Framework\TestCase {
/**
* @dataProvider provideTestDefaultValues
*/
public function testDefaultValues($value, $expectedValueNode) {
public function testDefaultValues($value, $expectedValueNode): void {
$node = $this->createParamBuilder('test')
->setDefault($value)
->getNode()
@@ -30,7 +30,7 @@ class ParamTest extends \PHPUnit\Framework\TestCase {
$this->assertEquals($expectedValueNode, $node->default);
}
public function provideTestDefaultValues() {
public static function provideTestDefaultValues() {
return [
[
null,
@@ -89,7 +89,7 @@ class ParamTest extends \PHPUnit\Framework\TestCase {
* @dataProvider provideTestNullableTypes
* @dataProvider provideTestUnionTypes
*/
public function testTypes($typeHint, $expectedType) {
public function testTypes($typeHint, $expectedType): void {
$node = $this->createParamBuilder('test')
->setType($typeHint)
->getNode()
@@ -107,7 +107,7 @@ class ParamTest extends \PHPUnit\Framework\TestCase {
$this->assertEquals($expectedType, $type);
}
public function provideTestTypes() {
public static function provideTestTypes() {
return [
['array', new Node\Identifier('array')],
['callable', new Node\Identifier('callable')],
@@ -127,7 +127,7 @@ class ParamTest extends \PHPUnit\Framework\TestCase {
];
}
public function provideTestNullableTypes() {
public static function provideTestNullableTypes() {
return [
['?array', new Node\NullableType(new Node\Identifier('array'))],
['?Some\Class', new Node\NullableType(new Node\Name('Some\Class'))],
@@ -142,7 +142,7 @@ class ParamTest extends \PHPUnit\Framework\TestCase {
];
}
public function provideTestUnionTypes() {
public static function provideTestUnionTypes() {
return [
[
new Node\UnionType([
@@ -169,19 +169,19 @@ class ParamTest extends \PHPUnit\Framework\TestCase {
];
}
public function testVoidTypeError() {
public function testVoidTypeError(): void {
$this->expectException(\LogicException::class);
$this->expectExceptionMessage('Parameter type cannot be void');
$this->createParamBuilder('test')->setType('void');
}
public function testInvalidTypeError() {
public function testInvalidTypeError(): void {
$this->expectException(\LogicException::class);
$this->expectExceptionMessage('Type must be a string, or an instance of Name, Identifier or ComplexType');
$this->createParamBuilder('test')->setType(new \stdClass());
}
public function testByRef() {
public function testByRef(): void {
$node = $this->createParamBuilder('test')
->makeByRef()
->getNode()
@@ -193,7 +193,7 @@ class ParamTest extends \PHPUnit\Framework\TestCase {
);
}
public function testVariadic() {
public function testVariadic(): void {
$node = $this->createParamBuilder('test')
->makeVariadic()
->getNode()
@@ -205,7 +205,7 @@ class ParamTest extends \PHPUnit\Framework\TestCase {
);
}
public function testMakePublic() {
public function testMakePublic(): void {
$node = $this->createParamBuilder('test')
->makePublic()
->getNode()
@@ -217,7 +217,7 @@ class ParamTest extends \PHPUnit\Framework\TestCase {
);
}
public function testMakeProtected() {
public function testMakeProtected(): void {
$node = $this->createParamBuilder('test')
->makeProtected()
->getNode()
@@ -227,9 +227,19 @@ class ParamTest extends \PHPUnit\Framework\TestCase {
new Node\Param(new Expr\Variable('test'), null, null, false, false, [], Modifiers::PROTECTED),
$node
);
$node = $this->createParamBuilder('test')
->makeProtectedSet()
->getNode()
;
$this->assertEquals(
new Node\Param(new Expr\Variable('test'), null, null, false, false, [], Modifiers::PROTECTED_SET),
$node
);
}
public function testMakePrivate() {
public function testMakePrivate(): void {
$node = $this->createParamBuilder('test')
->makePrivate()
->getNode()
@@ -239,9 +249,19 @@ class ParamTest extends \PHPUnit\Framework\TestCase {
new Node\Param(new Expr\Variable('test'), null, null, false, false, [], Modifiers::PRIVATE),
$node
);
$node = $this->createParamBuilder('test')
->makePrivateSet()
->getNode()
;
$this->assertEquals(
new Node\Param(new Expr\Variable('test'), null, null, false, false, [], Modifiers::PRIVATE_SET),
$node
);
}
public function testMakeReadonly() {
public function testMakeReadonly(): void {
$node = $this->createParamBuilder('test')
->makeReadonly()
->getNode()
@@ -253,7 +273,7 @@ class ParamTest extends \PHPUnit\Framework\TestCase {
);
}
public function testAddAttribute() {
public function testAddAttribute(): void {
$attribute = new Attribute(
new Name('Attr'),
[new Arg(new Int_(1), false, false, [], new Identifier('name'))]

@@ -3,6 +3,7 @@
namespace PhpParser\Builder;
use PhpParser\Comment;
use PhpParser\Error;
use PhpParser\Modifiers;
use PhpParser\Node\Arg;
use PhpParser\Node\Attribute;
@@ -10,6 +11,8 @@ use PhpParser\Node\AttributeGroup;
use PhpParser\Node\Expr;
use PhpParser\Node\Identifier;
use PhpParser\Node\Name;
use PhpParser\Node\PropertyHook;
use PhpParser\Node\PropertyItem;
use PhpParser\Node\Scalar;
use PhpParser\Node\Scalar\Int_;
use PhpParser\Node\Stmt;
@@ -19,7 +22,7 @@ class PropertyTest extends \PHPUnit\Framework\TestCase {
return new Property($name);
}
public function testModifiers() {
public function testModifiers(): void {
$node = $this->createPropertyBuilder('test')
->makePrivate()
->makeStatic()
@@ -29,9 +32,7 @@ class PropertyTest extends \PHPUnit\Framework\TestCase {
$this->assertEquals(
new Stmt\Property(
Modifiers::PRIVATE | Modifiers::STATIC,
[
new \PhpParser\Node\PropertyItem('test')
]
[new PropertyItem('test')]
),
$node
);
@@ -44,9 +45,7 @@ class PropertyTest extends \PHPUnit\Framework\TestCase {
$this->assertEquals(
new Stmt\Property(
Modifiers::PROTECTED,
[
new \PhpParser\Node\PropertyItem('test')
]
[new PropertyItem('test')]
),
$node
);
@@ -59,9 +58,7 @@ class PropertyTest extends \PHPUnit\Framework\TestCase {
$this->assertEquals(
new Stmt\Property(
Modifiers::PUBLIC,
[
new \PhpParser\Node\PropertyItem('test')
]
[new PropertyItem('test')]
),
$node
);
@@ -74,15 +71,40 @@ class PropertyTest extends \PHPUnit\Framework\TestCase {
$this->assertEquals(
new Stmt\Property(
Modifiers::READONLY,
[
new \PhpParser\Node\PropertyItem('test')
]
[new PropertyItem('test')]
),
$node
);
$node = $this->createPropertyBuilder('test')
->makeFinal()
->getNode();
$this->assertEquals(
new Stmt\Property(Modifiers::FINAL, [new PropertyItem('test')]),
$node);
$node = $this->createPropertyBuilder('test')
->makePrivateSet()
->getNode();
$this->assertEquals(
new Stmt\Property(Modifiers::PRIVATE_SET, [new PropertyItem('test')]),
$node);
$node = $this->createPropertyBuilder('test')
->makeProtectedSet()
->getNode();
$this->assertEquals(
new Stmt\Property(Modifiers::PROTECTED_SET, [new PropertyItem('test')]),
$node);
}
public function testDocComment() {
public function testAbstractWithoutHook() {
$this->expectException(Error::class);
$this->expectExceptionMessage('Only hooked properties may be declared abstract');
$this->createPropertyBuilder('test')->makeAbstract()->getNode();
}
public function testDocComment(): void {
$node = $this->createPropertyBuilder('test')
->setDocComment('/** Test */')
->getNode();
@@ -101,7 +123,7 @@ class PropertyTest extends \PHPUnit\Framework\TestCase {
/**
* @dataProvider provideTestDefaultValues
*/
public function testDefaultValues($value, $expectedValueNode) {
public function testDefaultValues($value, $expectedValueNode): void {
$node = $this->createPropertyBuilder('test')
->setDefault($value)
->getNode()
@@ -110,7 +132,7 @@ class PropertyTest extends \PHPUnit\Framework\TestCase {
$this->assertEquals($expectedValueNode, $node->props[0]->default);
}
public function testAddAttribute() {
public function testAddAttribute(): void {
$attribute = new Attribute(
new Name('Attr'),
[new Arg(new Int_(1), false, false, [], new Identifier('name'))]
@@ -136,7 +158,24 @@ class PropertyTest extends \PHPUnit\Framework\TestCase {
);
}
public function provideTestDefaultValues() {
public function testAddHook(): void {
$get = new PropertyHook('get', null);
$set = new PropertyHook('set', null);
$node = $this->createPropertyBuilder('test')
->addHook($get)
->addHook($set)
->makeAbstract()
->getNode();
$this->assertEquals(
new Stmt\Property(
Modifiers::ABSTRACT,
[new PropertyItem('test')],
[], null, [],
[$get, $set]),
$node);
}
public static function provideTestDefaultValues() {
return [
[
null,

@@ -23,7 +23,7 @@ class TraitTest extends \PHPUnit\Framework\TestCase {
return new Trait_($class);
}
public function testStmtAddition() {
public function testStmtAddition(): void {
$method1 = new Stmt\ClassMethod('test1');
$method2 = new Stmt\ClassMethod('test2');
$method3 = new Stmt\ClassMethod('test3');
@@ -49,7 +49,7 @@ class TraitTest extends \PHPUnit\Framework\TestCase {
]), $trait);
}
public function testInvalidStmtError() {
public function testInvalidStmtError(): void {
$this->expectException(\LogicException::class);
$this->expectExceptionMessage('Unexpected node of type "Stmt_Echo"');
$this->createTraitBuilder('Test')
@@ -57,7 +57,7 @@ class TraitTest extends \PHPUnit\Framework\TestCase {
;
}
public function testGetMethods() {
public function testGetMethods(): void {
$methods = [
new ClassMethod('foo'),
new ClassMethod('bar'),
@@ -77,7 +77,7 @@ class TraitTest extends \PHPUnit\Framework\TestCase {
$this->assertSame($methods, $trait->getMethods());
}
public function testGetProperties() {
public function testGetProperties(): void {
$properties = [
new Property(Modifiers::PUBLIC, [new PropertyItem('foo')]),
new Property(Modifiers::PUBLIC, [new PropertyItem('bar')]),
@@ -95,7 +95,7 @@ class TraitTest extends \PHPUnit\Framework\TestCase {
$this->assertSame($properties, $trait->getProperties());
}
public function testAddAttribute() {
public function testAddAttribute(): void {
$attribute = new Attribute(
new Name('Attr'),
[new Arg(new Int_(1), false, false, [], new Identifier('name'))]

@@ -12,7 +12,7 @@ class TraitUseAdaptationTest extends \PHPUnit\Framework\TestCase {
return new TraitUseAdaptation($trait, $method);
}
public function testAsMake() {
public function testAsMake(): void {
$builder = $this->createTraitUseAdaptationBuilder(null, 'foo');
$this->assertEquals(
@@ -36,7 +36,7 @@ class TraitUseAdaptationTest extends \PHPUnit\Framework\TestCase {
);
}
public function testInsteadof() {
public function testInsteadof(): void {
$node = $this->createTraitUseAdaptationBuilder('SomeTrait', 'foo')
->insteadof('AnotherTrait')
->getNode()
@@ -52,7 +52,7 @@ class TraitUseAdaptationTest extends \PHPUnit\Framework\TestCase {
);
}
public function testAsOnNotAlias() {
public function testAsOnNotAlias(): void {
$this->expectException(\LogicException::class);
$this->expectExceptionMessage('Cannot set alias for not alias adaptation buider');
$this->createTraitUseAdaptationBuilder('Test', 'foo')
@@ -61,7 +61,7 @@ class TraitUseAdaptationTest extends \PHPUnit\Framework\TestCase {
;
}
public function testInsteadofOnNotPrecedence() {
public function testInsteadofOnNotPrecedence(): void {
$this->expectException(\LogicException::class);
$this->expectExceptionMessage('Cannot add overwritten traits for not precedence adaptation buider');
$this->createTraitUseAdaptationBuilder('Test', 'foo')
@@ -70,7 +70,7 @@ class TraitUseAdaptationTest extends \PHPUnit\Framework\TestCase {
;
}
public function testInsteadofWithoutTrait() {
public function testInsteadofWithoutTrait(): void {
$this->expectException(\LogicException::class);
$this->expectExceptionMessage('Precedence adaptation must have trait');
$this->createTraitUseAdaptationBuilder(null, 'foo')
@@ -78,7 +78,7 @@ class TraitUseAdaptationTest extends \PHPUnit\Framework\TestCase {
;
}
public function testMakeOnNotAlias() {
public function testMakeOnNotAlias(): void {
$this->expectException(\LogicException::class);
$this->expectExceptionMessage('Cannot set access modifier for not alias adaptation buider');
$this->createTraitUseAdaptationBuilder('Test', 'foo')
@@ -87,7 +87,7 @@ class TraitUseAdaptationTest extends \PHPUnit\Framework\TestCase {
;
}
public function testMultipleMake() {
public function testMultipleMake(): void {
$this->expectException(\LogicException::class);
$this->expectExceptionMessage('Multiple access type modifiers are not allowed');
$this->createTraitUseAdaptationBuilder(null, 'foo')
@@ -96,7 +96,7 @@ class TraitUseAdaptationTest extends \PHPUnit\Framework\TestCase {
;
}
public function testUndefinedType() {
public function testUndefinedType(): void {
$this->expectException(\LogicException::class);
$this->expectExceptionMessage('Type of adaptation is not defined');
$this->createTraitUseAdaptationBuilder(null, 'foo')

@@ -10,7 +10,7 @@ class TraitUseTest extends \PHPUnit\Framework\TestCase {
return new TraitUse(...$traits);
}
public function testAnd() {
public function testAnd(): void {
$node = $this->createTraitUseBuilder('SomeTrait')
->and('AnotherTrait')
->getNode()
@@ -25,7 +25,7 @@ class TraitUseTest extends \PHPUnit\Framework\TestCase {
);
}
public function testWith() {
public function testWith(): void {
$node = $this->createTraitUseBuilder('SomeTrait')
->with(new Stmt\TraitUseAdaptation\Alias(null, 'foo', null, 'bar'))
->with((new TraitUseAdaptation(null, 'test'))->as('baz'))
@@ -41,7 +41,7 @@ class TraitUseTest extends \PHPUnit\Framework\TestCase {
);
}
public function testInvalidAdaptationNode() {
public function testInvalidAdaptationNode(): void {
$this->expectException(\LogicException::class);
$this->expectExceptionMessage('Adaptation must have type TraitUseAdaptation');
$this->createTraitUseBuilder('Test')

@@ -11,7 +11,7 @@ class UseTest extends \PHPUnit\Framework\TestCase {
return new Builder\Use_($name, $type);
}
public function testCreation() {
public function testCreation(): void {
$node = $this->createUseBuilder('Foo\Bar')->getNode();
$this->assertEquals(new Stmt\Use_([
new \PhpParser\Node\UseItem(new Name('Foo\Bar'), null)

@@ -15,12 +15,12 @@ class BuilderFactoryTest extends \PHPUnit\Framework\TestCase {
/**
* @dataProvider provideTestFactory
*/
public function testFactory($methodName, $className) {
public function testFactory($methodName, $className): void {
$factory = new BuilderFactory();
$this->assertInstanceOf($className, $factory->$methodName('test'));
}
public function provideTestFactory() {
public static function provideTestFactory() {
return [
['namespace', Builder\Namespace_::class],
['class', Builder\Class_::class],
@@ -38,12 +38,12 @@ class BuilderFactoryTest extends \PHPUnit\Framework\TestCase {
];
}
public function testFactoryClassConst() {
public function testFactoryClassConst(): void {
$factory = new BuilderFactory();
$this->assertInstanceOf(Builder\ClassConst::class, $factory->classConst('TEST', 1));
}
public function testAttribute() {
public function testAttribute(): void {
$factory = new BuilderFactory();
$this->assertEquals(
new Attribute(new Name('AttributeName'), [new Arg(
@@ -53,7 +53,7 @@ class BuilderFactoryTest extends \PHPUnit\Framework\TestCase {
);
}
public function testVal() {
public function testVal(): void {
// This method is a wrapper around BuilderHelpers::normalizeValue(),
// which is already tested elsewhere
$factory = new BuilderFactory();
@@ -63,7 +63,7 @@ class BuilderFactoryTest extends \PHPUnit\Framework\TestCase {
);
}
public function testConcat() {
public function testConcat(): void {
$factory = new BuilderFactory();
$varA = new Expr\Variable('a');
$varB = new Expr\Variable('b');
@@ -83,19 +83,19 @@ class BuilderFactoryTest extends \PHPUnit\Framework\TestCase {
);
}
public function testConcatOneError() {
public function testConcatOneError(): void {
$this->expectException(\LogicException::class);
$this->expectExceptionMessage('Expected at least two expressions');
(new BuilderFactory())->concat("a");
}
public function testConcatInvalidExpr() {
public function testConcatInvalidExpr(): void {
$this->expectException(\LogicException::class);
$this->expectExceptionMessage('Expected string or Expr');
(new BuilderFactory())->concat("a", 42);
}
public function testArgs() {
public function testArgs(): void {
$factory = new BuilderFactory();
$unpack = new Arg(new Expr\Variable('c'), false, true);
$this->assertEquals(
@@ -108,7 +108,7 @@ class BuilderFactoryTest extends \PHPUnit\Framework\TestCase {
);
}
public function testNamedArgs() {
public function testNamedArgs(): void {
$factory = new BuilderFactory();
$this->assertEquals(
[
@@ -119,7 +119,7 @@ class BuilderFactoryTest extends \PHPUnit\Framework\TestCase {
);
}
public function testCalls() {
public function testCalls(): void {
$factory = new BuilderFactory();
// Simple function call
@@ -195,7 +195,7 @@ class BuilderFactoryTest extends \PHPUnit\Framework\TestCase {
);
}
public function testConstFetches() {
public function testConstFetches(): void {
$factory = new BuilderFactory();
$this->assertEquals(
new Expr\ConstFetch(new Name('FOO')),
@@ -215,7 +215,7 @@ class BuilderFactoryTest extends \PHPUnit\Framework\TestCase {
);
}
public function testVar() {
public function testVar(): void {
$factory = new BuilderFactory();
$this->assertEquals(
new Expr\Variable("foo"),
@@ -227,7 +227,7 @@ class BuilderFactoryTest extends \PHPUnit\Framework\TestCase {
);
}
public function testPropertyFetch() {
public function testPropertyFetch(): void {
$f = new BuilderFactory();
$this->assertEquals(
new Expr\PropertyFetch(new Expr\Variable('foo'), 'bar'),
@@ -243,31 +243,31 @@ class BuilderFactoryTest extends \PHPUnit\Framework\TestCase {
);
}
public function testInvalidIdentifier() {
public function testInvalidIdentifier(): void {
$this->expectException(\LogicException::class);
$this->expectExceptionMessage('Expected string or instance of Node\Identifier');
(new BuilderFactory())->classConstFetch('Foo', new Name('foo'));
}
public function testInvalidIdentifierOrExpr() {
public function testInvalidIdentifierOrExpr(): void {
$this->expectException(\LogicException::class);
$this->expectExceptionMessage('Expected string or instance of Node\Identifier or Node\Expr');
(new BuilderFactory())->staticCall('Foo', new Name('bar'));
}
public function testInvalidNameOrExpr() {
public function testInvalidNameOrExpr(): void {
$this->expectException(\LogicException::class);
$this->expectExceptionMessage('Name must be a string or an instance of Node\Name or Node\Expr');
(new BuilderFactory())->funcCall(new Node\Stmt\Return_());
}
public function testInvalidVar() {
public function testInvalidVar(): void {
$this->expectException(\LogicException::class);
$this->expectExceptionMessage('Variable name must be string or Expr');
(new BuilderFactory())->var(new Node\Stmt\Return_());
}
public function testIntegration() {
public function testIntegration(): void {
$factory = new BuilderFactory();
$node = $factory->namespace('Name\Space')
->addStmt($factory->use('Foo\Bar\SomeOtherClass'))
@@ -365,7 +365,10 @@ abstract class SomeClass extends SomeOtherClass implements A\Few, \Interfaces
* @param SomeClass And takes a parameter
*/
abstract public function someMethod(SomeClass $someParam);
protected function anotherMethod(#[TaggedIterator('app.handlers')] $someParam = 'test')
protected function anotherMethod(
#[TaggedIterator('app.handlers')]
$someParam = 'test'
)
{
print $someParam;
}

@@ -4,12 +4,13 @@ namespace PhpParser;
use PhpParser\Builder\Class_;
use PhpParser\Node\Identifier;
use PhpParser\Node\Name\FullyQualified;
use PhpParser\Node\Scalar;
use PhpParser\Node\Stmt;
use PhpParser\Node\Expr;
class BuilderHelpersTest extends \PHPUnit\Framework\TestCase {
public function testNormalizeNode() {
public function testNormalizeNode(): void {
$builder = new Class_('SomeClass');
$this->assertEquals($builder->getNode(), BuilderHelpers::normalizeNode($builder));
@@ -21,7 +22,7 @@ class BuilderHelpersTest extends \PHPUnit\Framework\TestCase {
BuilderHelpers::normalizeNode('test');
}
public function testNormalizeStmt() {
public function testNormalizeStmt(): void {
$stmt = new Node\Stmt\Class_('Class');
$this->assertSame($stmt, BuilderHelpers::normalizeStmt($stmt));
@@ -35,13 +36,13 @@ class BuilderHelpersTest extends \PHPUnit\Framework\TestCase {
BuilderHelpers::normalizeStmt(new Node\Attribute(new Node\Name('Test')));
}
public function testNormalizeStmtInvalidType() {
public function testNormalizeStmtInvalidType(): void {
$this->expectException(\LogicException::class);
$this->expectExceptionMessage('Expected node or builder object');
BuilderHelpers::normalizeStmt('test');
}
public function testNormalizeIdentifier() {
public function testNormalizeIdentifier(): void {
$identifier = new Node\Identifier('fn');
$this->assertSame($identifier, BuilderHelpers::normalizeIdentifier($identifier));
$this->assertEquals($identifier, BuilderHelpers::normalizeIdentifier('fn'));
@@ -51,7 +52,7 @@ class BuilderHelpersTest extends \PHPUnit\Framework\TestCase {
BuilderHelpers::normalizeIdentifier(1);
}
public function testNormalizeIdentifierOrExpr() {
public function testNormalizeIdentifierOrExpr(): void {
$identifier = new Node\Identifier('fn');
$this->assertSame($identifier, BuilderHelpers::normalizeIdentifierOrExpr($identifier));
@@ -64,7 +65,7 @@ class BuilderHelpersTest extends \PHPUnit\Framework\TestCase {
BuilderHelpers::normalizeIdentifierOrExpr(1);
}
public function testNormalizeName() {
public function testNormalizeName(): void {
$name = new Node\Name('test');
$this->assertSame($name, BuilderHelpers::normalizeName($name));
$this->assertEquals(
@@ -82,13 +83,13 @@ class BuilderHelpersTest extends \PHPUnit\Framework\TestCase {
BuilderHelpers::normalizeName('');
}
public function testNormalizeNameInvalidType() {
public function testNormalizeNameInvalidType(): void {
$this->expectException(\LogicException::class);
$this->expectExceptionMessage('Name must be a string or an instance of Node\Name');
BuilderHelpers::normalizeName(1);
}
public function testNormalizeNameOrExpr() {
public function testNormalizeNameOrExpr(): void {
$expr = new Expr\Variable('fn');
$this->assertSame($expr, BuilderHelpers::normalizeNameOrExpr($expr));
@@ -109,13 +110,13 @@ class BuilderHelpersTest extends \PHPUnit\Framework\TestCase {
BuilderHelpers::normalizeNameOrExpr('');
}
public function testNormalizeNameOrExpInvalidType() {
public function testNormalizeNameOrExpInvalidType(): void {
$this->expectException(\LogicException::class);
$this->expectExceptionMessage('Name must be a string or an instance of Node\Name or Node\Expr');
BuilderHelpers::normalizeNameOrExpr(1);
}
public function testNormalizeType() {
public function testNormalizeType(): void {
$this->assertEquals(new Node\Identifier('array'), BuilderHelpers::normalizeType('array'));
$this->assertEquals(new Node\Identifier('callable'), BuilderHelpers::normalizeType('callable'));
$this->assertEquals(new Node\Identifier('string'), BuilderHelpers::normalizeType('string'));
@@ -156,25 +157,25 @@ class BuilderHelpersTest extends \PHPUnit\Framework\TestCase {
BuilderHelpers::normalizeType(1);
}
public function testNormalizeTypeNullableVoid() {
public function testNormalizeTypeNullableVoid(): void {
$this->expectException(\LogicException::class);
$this->expectExceptionMessage('void type cannot be nullable');
BuilderHelpers::normalizeType('?void');
}
public function testNormalizeTypeNullableMixed() {
public function testNormalizeTypeNullableMixed(): void {
$this->expectException(\LogicException::class);
$this->expectExceptionMessage('mixed type cannot be nullable');
BuilderHelpers::normalizeType('?mixed');
}
public function testNormalizeTypeNullableNever() {
public function testNormalizeTypeNullableNever(): void {
$this->expectException(\LogicException::class);
$this->expectExceptionMessage('never type cannot be nullable');
BuilderHelpers::normalizeType('?never');
}
public function testNormalizeValue() {
public function testNormalizeValue(): void {
$expression = new Scalar\Int_(1);
$this->assertSame($expression, BuilderHelpers::normalizeValue($expression));
@@ -200,7 +201,7 @@ class BuilderHelpersTest extends \PHPUnit\Framework\TestCase {
BuilderHelpers::normalizeValue(new \stdClass());
}
public function testNormalizeDocComment() {
public function testNormalizeDocComment(): void {
$docComment = new Comment\Doc('Some doc comment');
$this->assertSame($docComment, BuilderHelpers::normalizeDocComment($docComment));
@@ -211,7 +212,7 @@ class BuilderHelpersTest extends \PHPUnit\Framework\TestCase {
BuilderHelpers::normalizeDocComment(1);
}
public function testNormalizeAttribute() {
public function testNormalizeAttribute(): void {
$attribute = new Node\Attribute(new Node\Name('Test'));
$attributeGroup = new Node\AttributeGroup([$attribute]);
@@ -222,4 +223,14 @@ class BuilderHelpersTest extends \PHPUnit\Framework\TestCase {
$this->expectExceptionMessage('Attribute must be an instance of PhpParser\Node\Attribute or PhpParser\Node\AttributeGroup');
BuilderHelpers::normalizeAttribute('test');
}
public function testNormalizeValueEnum() {
if (\PHP_VERSION_ID <= 80100) {
$this->markTestSkipped('Enums are supported since PHP 8.1');
}
include __DIR__ . '/../fixtures/Suit.php';
$this->assertEquals(new Expr\ClassConstFetch(new FullyQualified(\Suit::class), new Identifier('Hearts')), BuilderHelpers::normalizeValue(\Suit::Hearts));
}
}

Some files were not shown because too many files have changed in this diff Show More