Compare commits

..

85 Commits

Author SHA1 Message Date
dc47765670 Support every argument syntax for clone() (#1092)
See php/php-src#18938
2025-07-02 22:09:49 +02:00
8ab65b4adc Support declaring functions with name clone (#1090)
For use in stubs.

see 9c7a3f8d8f
see https://wiki.php.net/rfc/clone_with_v2
see php/php-src#18919
2025-06-23 14:35:24 +02:00
7fc3bcf970 Minor spelling now longer -> no longer (#1088) 2025-06-01 10:25:00 +02:00
acf8f5ef01 Minor spelling creatForHostVersion() -> createForHostVersion() (#1087) 2025-06-01 10:24:40 +02:00
ae59794362 Release PHP-Parser 5.5.0 2025-05-31 10:24:38 +02:00
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
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
c9d0b6c9f2 Support WeakReferences in NodeConnectingVisitor (#1057)
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
14f9c9df7f Use more precise list-types for getter methods 2025-02-18 22:17:03 +01:00
fa02db3f30 Remove useless foreach key and cast string, and fix invalid @var definition in test (#1061) 2025-01-17 22:41:39 +01:00
d4fce83c2c Remove useless cast (string) and @var doc 2025-01-16 21:27:18 +01:00
7d3039c378 Update to phpstan 2.0 2024-12-30 12:14:29 +01:00
447a020a1f Release PHP-Parser 5.4.0 2024-12-30 12:07:19 +01:00
45f70ed80a Simplify NodeTraverser->traverseNode() 2024-12-30 11:10:07 +01:00
74a361814d Simplify NodeTraverser->traverseArray() 2024-12-30 11:10:07 +01:00
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 #1053.
2024-12-27 16:56:00 +01:00
8bb415902e Fix cs 2024-12-27 16:33:38 +01:00
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
d20a197ca7 Emit error - Multiple properties cannot share the same hooks
Closes GH-1052.
2024-12-27 16:19:46 +01:00
62dee28027 Rename ParserAbstract method checkPropertyHookList to checkEmptyPropertyHookList 2024-12-27 16:13:47 +01:00
b396e9e0d6 Add missing newline after usage message 2024-12-27 16:13:29 +01:00
f212bb7afb Add PropertyHook::isFinal() helper method with tests 2024-12-13 23:15:12 +01:00
f43324a074 Missing flags subNode description in PropertyHook constructor PHPDoc 2024-12-13 23:15:12 +01:00
73b160f8c4 Add flags helper methods Property::isAbstract() and Property::isFinal() 2024-12-13 23:15:12 +01:00
4f9dc8b0f5 Promoted properties with hooks do not need visibility modifier 2024-12-11 10:17:55 +01:00
469377f4a8 Fix classmap authoritative autoloading (#1046)
This patch resolves #1045 by adding dummy class definitions
behind if (false) checks that are picked up the authoritative
autoloader.
2024-12-06 22:00:17 +01:00
74d3f7fc24 Removed reudndant token generation 2024-11-24 09:54:02 +01:00
25828ea952 Updated target.php to new PHP-Parser version 2024-11-24 09:54:02 +01:00
05ed79595b Removed unrecheable statement 2024-11-19 21:17:27 +01:00
954f7a411f More precise toLowerString() return type 2024-11-05 21:19:09 +01:00
8eea230464 Release PHP-Parser 5.3.1 2024-10-08 20:51:32 +02:00
9c7a3f8d8f Support declaring functions with name exit/die
For use in stubs.

Fixes #1030.
2024-10-08 20:46:50 +02:00
26573ea64f Makefile works on windows 2024-10-07 20:54:36 +02:00
3abf7425cd Release PHP-Parser 5.3.0 2024-09-29 15:56:26 +02:00
961f158f6d Avoid negative indendation in formatting-preserving printer
Fixes #1015.
2024-09-29 15:34:59 +02:00
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
26a0197186 Use PHP 8.4 for the PHP 7.4 integration test 2024-09-21 16:03:21 +02:00
aedfcc23cd Include trailing semicolon in GroupUse
Fixes https://github.com/nikic/PHP-Parser/issues/1026.
2024-09-21 15:55:21 +02:00
8d09ba87f4 Support PropertyHooks in NameResolver 2024-09-17 21:33:51 +02:00
23c79fbbfb Release PHP-Parser 5.2.0 2024-09-15 18:40:33 +02:00
b9c8374498 Run PHP 8.4 integration tests 2024-09-15 18:32:49 +02:00
d0826bd3e4 Add changelog entries 2024-09-15 18:28:36 +02:00
5ea6c2938b Exclude integration test 2024-09-05 21:41:03 +02:00
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
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
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
018da15f3a Add token emulation support for asymmetric visibility modifiers 2024-08-31 20:41:07 +02:00
ba14437165 chore: fix comment
Signed-off-by: evenevent <digping@icloud.com>
2024-08-15 09:22:56 +02:00
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
514f71024e Fix for new php-cs-fixer version 2024-08-11 17:03:56 +02:00
6a97061265 Normalize enum value to ClassConstFetch
Fixes #930

(cherry picked from commit 8a21ec3182)
2024-08-11 16:58:28 +02:00
e3f223f623 Fix expected json dump for old PHP versions 2024-07-28 19:05:16 +02:00
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
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
3beee6ec77 Add Modifiers::toString() helper
Convert an integer modifier into a string.
2024-07-28 17:09:14 +02:00
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
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
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
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
d8235a2701 [8.4] Add support for __PROPERTY__ magic constant
Part of property hooks RFC.
2024-07-14 18:52:47 +02:00
5973c30a46 Update JSON_representation.markdown
Fix the invalid JSON structure
2024-07-13 15:46:21 +02:00
b917ba7b9c Add PHP 8.4 to CI matrix
Not using it in the integration tests yet.
2024-07-13 11:11:15 +02:00
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
683130c2ff Release PHP-Parser 5.1.0 2024-07-01 22:03:41 +02:00
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 #1009.
2024-07-01 21:51:42 +02:00
3ef0811e45 Fix a typo: Dereferencable to Dereferenceable 2024-06-12 20:31:58 +02:00
db1963f9ea Fix a typo: Dereferenceing to Dereferencing 2024-06-12 20:29:11 +02:00
daaadc3bae Adjust tests to be compatible with PHPUnit 10 (#998)
This avoids warnings on PHPUnit 10, without actually
switching to PHPUnit 10.
2024-06-03 08:24:19 +02:00
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
d57da64d7c Add missing void return types (#997) 2024-05-31 09:32:45 +02:00
d327cf2acf Updated actions/checkout to v4 2024-05-21 18:12:19 +02:00
c5ee33df86 Declare more precise phpdoc types (#993) 2024-04-19 21:04:10 +09:00
46be4560c4 Use PHP 8.3 for more CI jobs 2024-03-17 11:24:36 +01:00
f2e037f8ea Make phpunit fail on deprecation warnings (#989)
(cherry picked from commit 4d36e9c16f)
2024-03-17 10:06:45 +01:00
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
09691fc86e Prevent off-by-one errors in line-number related methods 2024-03-13 20:24:50 +01:00
139676794d Release PHP-Parser 5.0.2 2024-03-05 21:51:40 +01:00
70c96493b4 Fix indentation detection after opening tag
Fixes #982.
2024-03-02 18:59:44 +01:00
ec02613432 Update PhpVersion::getNewestSupported() to PHP 8.3 2024-03-02 08:27:21 +01:00
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 #980.
2024-02-21 21:13:45 +01:00
2218c2252c Release PHP-Parser 5.0.1 2024-02-21 20:24:10 +01:00
ce019e9ad7 Add dev tool files to export-ignore 2024-01-14 10:02:54 +01:00
ff095c3c65 Check for tokens with non-integer ID
Fixes #974.
2024-01-10 20:34:09 +01:00
d619c8b4e6 Fix typo in upgrading guide
Fixes #973.
2024-01-09 20:23:09 +01:00
eb036d5a09 Add instructions for adding new syntax support 2024-01-07 18:54:10 +01:00
191 changed files with 9434 additions and 4554 deletions

4
.gitattributes vendored
View File

@ -7,7 +7,11 @@
.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

View File

@ -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,12 +110,12 @@ 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: |

View File

@ -1,3 +1,122 @@
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)
--------------------------

View File

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

View File

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

View File

@ -6,7 +6,7 @@ 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] (current; 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] (supported; for running on PHP >= 7.0; for parsing PHP 5.2 to PHP 8.3).

View File

@ -37,7 +37,7 @@ The `ParserFactory::create()` method has been removed in favor of three new meth
* `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 `creatForHostVersion()` are available since PHP-Parser 4.18.0, to allow libraries to support PHP-Parser 4 and 5 at the same time more easily.
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.
@ -308,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`.
@ -519,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);

View File

@ -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": {

View File

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

View File

@ -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')`.

View File

@ -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\""
},

View File

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

View File

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

View File

@ -81,7 +81,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 +106,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 +256,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 +269,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]; }
;
@ -460,15 +467,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]]; }
;
@ -671,18 +680,23 @@ 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; }
;
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); }
@ -771,6 +785,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[]; }
;
@ -780,14 +810,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
;
@ -828,8 +862,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); }
@ -901,6 +941,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; }
@ -926,6 +969,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
;
@ -959,6 +1035,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]; }
@ -1030,10 +1107,8 @@ 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_EXIT ctor_arguments
{ $$ = $this->createExitExpr($1, #1, $2, attributes()); }
| '@' expr { $$ = Expr\ErrorSuppress[$2]; }
| scalar
| '`' backticks_expr '`' { $$ = Expr\ShellExec[$2]; }
@ -1069,12 +1144,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; }
@ -1126,12 +1210,7 @@ class_name_reference:
class_name_or_var:
class_name
| fully_dereferencable
;
exit_expr:
/* empty */ { $$ = null; }
| '(' optional_expr ')' { $$ = $2; }
| fully_dereferenceable
;
backticks_expr:
@ -1156,6 +1235,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:
@ -1175,7 +1255,7 @@ 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);
@ -1192,7 +1272,7 @@ 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
@ -1208,32 +1288,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]; }
;
@ -1245,9 +1329,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]; }
;
@ -1271,7 +1355,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

View File

@ -23,6 +23,7 @@ function preprocessGrammar($code) {
$code = resolveNodes($code);
$code = resolveMacros($code);
$code = resolveStackAccess($code);
$code = str_replace('$this', '$self', $code);
return $code;
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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);
}
/**
@ -248,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);
}
}
}
@ -268,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;
}
}

View File

@ -5,15 +5,13 @@ 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\PropertyTokenEmulator;
use PhpParser\Lexer\TokenEmulator\ReadonlyFunctionTokenEmulator;
use PhpParser\Lexer\TokenEmulator\ReadonlyTokenEmulator;
use PhpParser\Lexer\TokenEmulator\ReverseEmulator;
@ -47,6 +45,8 @@ class Emulative extends Lexer {
new ReadonlyTokenEmulator(),
new ExplicitOctalEmulator(),
new ReadonlyFunctionTokenEmulator(),
new PropertyTokenEmulator(),
new AsymmetricVisibilityTokenEmulator(),
];
// Collect emulators that are relevant for the PHP version we're running

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -21,6 +21,8 @@ class Param extends NodeAbstract {
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.
@ -33,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;
@ -49,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 {
@ -63,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 {
@ -81,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);
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -145,7 +145,7 @@ class NodeDumper {
} elseif ($node instanceof Comment) {
$this->res .= \str_replace("\n", $this->nl, $node->getReformattedText());
} elseif (\is_string($node)) {
$this->res .= \str_replace("\n", $this->nl, (string)$node);
$this->res .= \str_replace("\n", $this->nl, $node);
} elseif (\is_int($node) || \is_float($node)) {
$this->res .= $node;
} elseif (null === $node) {
@ -185,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 . ')';

View File

@ -99,7 +99,14 @@ class NodeTraverser implements NodeTraverserInterface {
if ($this->stopTraversal) {
break;
}
} elseif ($subNode instanceof Node) {
continue;
}
if (!$subNode instanceof Node) {
continue;
}
$traverseChildren = true;
$visitorIndex = -1;
@ -163,7 +170,6 @@ class NodeTraverser implements NodeTraverserInterface {
}
}
}
}
/**
* Recursively traverse array (usually of nodes).
@ -176,7 +182,13 @@ class NodeTraverser implements NodeTraverserInterface {
$doNodes = [];
foreach ($nodes as $i => $node) {
if ($node instanceof Node) {
if (!$node instanceof Node) {
if (\is_array($node)) {
throw new \LogicException('Invalid node structure: Contains nested arrays');
}
continue;
}
$traverseChildren = true;
$visitorIndex = -1;
@ -245,9 +257,6 @@ class NodeTraverser implements NodeTraverserInterface {
}
}
}
} elseif (\is_array($node)) {
throw new \LogicException('Invalid node structure: Contains nested arrays');
}
}
if (!empty($doNodes)) {

View File

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

View File

@ -110,10 +110,17 @@ 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);

View File

@ -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,13 +40,27 @@ 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')) {
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;
}

View File

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

View File

@ -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,7 @@ 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 {
@ -318,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];
}
@ -409,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 {
@ -1137,32 +1139,12 @@ abstract class ParserAbstract implements Parser {
}
protected function checkClassConst(ClassConst $node, int $modifierPos): void {
if ($node->flags & Modifiers::STATIC) {
foreach ([Modifiers::STATIC, Modifiers::ABSTRACT, Modifiers::READONLY] as $modifier) {
if ($node->flags & $modifier) {
$this->emitError(new Error(
"Cannot use 'static' as constant modifier",
"Cannot use '" . Modifiers::toString($modifier) . "' 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)));
}
}
@ -1178,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.
*
@ -1190,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.
for ($i = 0; $i < 256; ++$i) {
$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) {
}
foreach ($this->symbolToName as $name) {
if ($name[0] === 'T') {
$tokenMap[\constant($name)] = constant(static::class . '::' . $name);
}
}
// T_OPEN_TAG_WITH_ECHO with dropped T_OPEN_TAG results in T_ECHO
$tokenMap[$i] = static::T_ECHO;
} elseif (\T_CLOSE_TAG === $i) {
$tokenMap[\T_OPEN_TAG_WITH_ECHO] = static::T_ECHO;
// 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);
}
}
}
$tokenMap[\T_CLOSE_TAG] = ord(';');
// 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;
// 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) {

View File

@ -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, 4);
}
/**
@ -161,4 +161,11 @@ class PhpVersion {
public function supportsUnicodeEscapes(): bool {
return $this->id >= 70000;
}
/*
* Whether this version supports attributes.
*/
public function supportsAttributes(): bool {
return $this->id >= 80000;
}
}

View File

@ -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 {
@ -651,7 +656,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 +688,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 +832,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 +841,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 +872,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 {
@ -1164,6 +1181,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 = '';

View File

@ -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;
@ -76,7 +77,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],
@ -105,6 +106,15 @@ abstract class PrettyPrinterAbstract implements PrettyPrinter {
/** @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 +179,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 +201,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 +230,29 @@ abstract class PrettyPrinterAbstract implements PrettyPrinter {
*/
protected function setIndentLevel(int $level): void {
$this->indentLevel = $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 +564,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 +736,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 +839,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();
@ -1195,6 +1223,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 ' : '');
}
@ -1517,6 +1548,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",
@ -1554,10 +1586,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",
@ -1597,6 +1634,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:
@ -1641,6 +1679,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

View File

@ -17,6 +17,11 @@ 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-Parser might be used together with another library that also emulates some or all
@ -26,6 +31,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(

View File

@ -1,236 +1,259 @@
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: '#^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\\\\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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -9,7 +9,7 @@ class CodeParsingTest extends CodeTestAbstract {
/**
* @dataProvider provideTestParse
*/
public function testParse($name, $code, $expected, $modeLine) {
public function testParse($name, $code, $expected, $modeLine): void {
$modes = $this->parseModeLine($modeLine);
$parser = $this->createParser($modes['version'] ?? null);
list($stmts, $output) = $this->getParseOutput($parser, $code, $modes);
@ -50,8 +50,8 @@ class CodeParsingTest extends CodeTestAbstract {
return [$stmts, canonicalize($output)];
}
public function provideTestParse() {
return $this->getTests(__DIR__ . '/../code/parser', 'test');
public static function provideTestParse() {
return self::getTests(__DIR__ . '/../code/parser', 'test');
}
private function formatErrorMessage(Error $e, $code) {
@ -62,13 +62,13 @@ class CodeParsingTest extends CodeTestAbstract {
return $e->getMessage();
}
private function checkAttributes($stmts) {
private function checkAttributes($stmts): void {
if ($stmts === null) {
return;
}
$traverser = new NodeTraverser(new class () extends NodeVisitorAbstract {
public function enterNode(Node $node) {
public function enterNode(Node $node): void {
$startLine = $node->getStartLine();
$endLine = $node->getEndLine();
$startFilePos = $node->getStartFilePos();

View File

@ -3,7 +3,7 @@
namespace PhpParser;
abstract class CodeTestAbstract extends \PHPUnit\Framework\TestCase {
protected function getTests($directory, $fileExtension, $chunksPerTest = 2) {
protected static function getTests($directory, $fileExtension, $chunksPerTest = 2) {
$parser = new CodeTestParser();
$allTests = [];
foreach (filesInDir($directory, $fileExtension) as $fileName => $fileContents) {

View File

@ -24,7 +24,7 @@ class CodeTestParser {
// multiple sections possible with always two forming a pair
$chunks = array_chunk($parts, $chunksPerTest);
$tests = [];
foreach ($chunks as $i => $chunk) {
foreach ($chunks as $chunk) {
$lastPart = array_pop($chunk);
list($lastPart, $mode) = $this->extractMode($lastPart);
$tests[] = [$mode, array_merge($chunk, [$lastPart])];
@ -61,7 +61,7 @@ class CodeTestParser {
return [$expected, null];
}
$expected = (string) substr($expected, $firstNewLine + 1);
$expected = substr($expected, $firstNewLine + 1);
return [$expected, substr($firstLine, 2)];
}
}

View File

@ -3,7 +3,7 @@
namespace PhpParser;
class CommentTest extends \PHPUnit\Framework\TestCase {
public function testGetters() {
public function testGetters(): void {
$comment = new Comment('/* Some comment */',
1, 10, 2, 1, 27, 2);
@ -20,12 +20,12 @@ class CommentTest extends \PHPUnit\Framework\TestCase {
/**
* @dataProvider provideTestReformatting
*/
public function testReformatting($commentText, $reformattedText) {
public function testReformatting($commentText, $reformattedText): void {
$comment = new Comment($commentText);
$this->assertSame($reformattedText, $comment->getReformattedText());
}
public function provideTestReformatting() {
public static function provideTestReformatting() {
return [
['// Some text', '// Some text'],
['/* Some text */', '/* Some text */'],

View File

@ -13,7 +13,7 @@ class CompatibilityTest extends \PHPUnit\Framework\TestCase {
* @runInSeparateProcess
* @preserveGlobalState disabled
*/
public function testAliases1() {
public function testAliases1(): void {
$var = new Expr\Variable('x');
$node = new Node\ClosureUse($var);
$this->assertTrue($node instanceof Expr\ClosureUse);
@ -41,7 +41,7 @@ class CompatibilityTest extends \PHPUnit\Framework\TestCase {
* @runInSeparateProcess
* @preserveGlobalState disabled
*/
public function testAliases2() {
public function testAliases2(): void {
$var = new Expr\Variable('x');
$node = new Node\Expr\ClosureUse($var);
$this->assertTrue($node instanceof Node\ClosureUse);

View File

@ -7,14 +7,14 @@ use PhpParser\Node\Scalar;
class ConstExprEvaluatorTest extends \PHPUnit\Framework\TestCase {
/** @dataProvider provideTestEvaluate */
public function testEvaluate($exprString, $expected) {
public function testEvaluate($exprString, $expected): void {
$parser = new Parser\Php7(new Lexer());
$expr = $parser->parse('<?php ' . $exprString . ';')[0]->expr;
$evaluator = new ConstExprEvaluator();
$this->assertSame($expected, $evaluator->evaluateDirectly($expr));
}
public function provideTestEvaluate() {
public static function provideTestEvaluate() {
return [
['1', 1],
['1.0', 1.0],
@ -74,14 +74,14 @@ class ConstExprEvaluatorTest extends \PHPUnit\Framework\TestCase {
];
}
public function testEvaluateFails() {
public function testEvaluateFails(): void {
$this->expectException(ConstExprEvaluationException::class);
$this->expectExceptionMessage('Expression of type Expr_Variable cannot be evaluated');
$evaluator = new ConstExprEvaluator();
$evaluator->evaluateDirectly(new Expr\Variable('a'));
}
public function testEvaluateFallback() {
public function testEvaluateFallback(): void {
$evaluator = new ConstExprEvaluator(function (Expr $expr) {
if ($expr instanceof Scalar\MagicConst\Line) {
return 42;
@ -98,7 +98,7 @@ class ConstExprEvaluatorTest extends \PHPUnit\Framework\TestCase {
/**
* @dataProvider provideTestEvaluateSilently
*/
public function testEvaluateSilently($expr, $exception, $msg) {
public function testEvaluateSilently($expr, $exception, $msg): void {
$evaluator = new ConstExprEvaluator();
try {
@ -115,7 +115,7 @@ class ConstExprEvaluatorTest extends \PHPUnit\Framework\TestCase {
}
}
public function provideTestEvaluateSilently() {
public static function provideTestEvaluateSilently() {
return [
[
new Expr\BinaryOp\Mod(new Scalar\Int_(42), new Scalar\Int_(0)),

View File

@ -5,7 +5,7 @@ namespace PhpParser\ErrorHandler;
use PhpParser\Error;
class CollectingTest extends \PHPUnit\Framework\TestCase {
public function testHandleError() {
public function testHandleError(): void {
$errorHandler = new Collecting();
$this->assertFalse($errorHandler->hasErrors());
$this->assertEmpty($errorHandler->getErrors());

View File

@ -5,7 +5,7 @@ namespace PhpParser\ErrorHandler;
use PhpParser\Error;
class ThrowingTest extends \PHPUnit\Framework\TestCase {
public function testHandleError() {
public function testHandleError(): void {
$this->expectException(Error::class);
$this->expectExceptionMessage('Test');
$errorHandler = new Throwing();

View File

@ -22,7 +22,7 @@ class ErrorTest extends \PHPUnit\Framework\TestCase {
/**
* @depends testConstruct
*/
public function testSetMessageAndLine(Error $error) {
public function testSetMessageAndLine(Error $error): void {
$error->setRawMessage('Some other error');
$this->assertSame('Some other error', $error->getRawMessage());
@ -31,7 +31,7 @@ class ErrorTest extends \PHPUnit\Framework\TestCase {
$this->assertSame('Some other error on line 15', $error->getMessage());
}
public function testUnknownLine() {
public function testUnknownLine(): void {
$error = new Error('Some error');
$this->assertSame(-1, $error->getStartLine());
@ -40,7 +40,7 @@ class ErrorTest extends \PHPUnit\Framework\TestCase {
}
/** @dataProvider provideTestColumnInfo */
public function testColumnInfo($code, $startPos, $endPos, $startColumn, $endColumn) {
public function testColumnInfo($code, $startPos, $endPos, $startColumn, $endColumn): void {
$error = new Error('Some error', [
'startFilePos' => $startPos,
'endFilePos' => $endPos,
@ -51,7 +51,7 @@ class ErrorTest extends \PHPUnit\Framework\TestCase {
$this->assertSame($endColumn, $error->getEndColumn($code));
}
public function provideTestColumnInfo() {
public static function provideTestColumnInfo() {
return [
// Error at "bar"
["<?php foo bar baz", 10, 12, 11, 13],
@ -72,7 +72,7 @@ class ErrorTest extends \PHPUnit\Framework\TestCase {
];
}
public function testNoColumnInfo() {
public function testNoColumnInfo(): void {
$error = new Error('Some error', ['startLine' => 3]);
$this->assertFalse($error->hasColumnInfo());
@ -90,7 +90,7 @@ class ErrorTest extends \PHPUnit\Framework\TestCase {
}
}
public function testInvalidPosInfo() {
public function testInvalidPosInfo(): void {
$this->expectException(\RuntimeException::class);
$this->expectExceptionMessage('Invalid position information');
$error = new Error('Some error', [

View File

@ -28,7 +28,7 @@ class DifferTest extends \PHPUnit\Framework\TestCase {
}
/** @dataProvider provideTestDiff */
public function testDiff($oldStr, $newStr, $expectedDiffStr) {
public function testDiff($oldStr, $newStr, $expectedDiffStr): void {
$differ = new Differ(function ($a, $b) {
return $a === $b;
});
@ -36,7 +36,7 @@ class DifferTest extends \PHPUnit\Framework\TestCase {
$this->assertSame($expectedDiffStr, $this->formatDiffString($diff));
}
public function provideTestDiff() {
public static function provideTestDiff() {
return [
['abc', 'abc', 'abc'],
['abc', 'abcdef', 'abc+d+e+f'],
@ -49,7 +49,7 @@ class DifferTest extends \PHPUnit\Framework\TestCase {
}
/** @dataProvider provideTestDiffWithReplacements */
public function testDiffWithReplacements($oldStr, $newStr, $expectedDiffStr) {
public function testDiffWithReplacements($oldStr, $newStr, $expectedDiffStr): void {
$differ = new Differ(function ($a, $b) {
return $a === $b;
});
@ -57,7 +57,7 @@ class DifferTest extends \PHPUnit\Framework\TestCase {
$this->assertSame($expectedDiffStr, $this->formatDiffString($diff));
}
public function provideTestDiffWithReplacements() {
public static function provideTestDiffWithReplacements() {
return [
['abcde', 'axyze', 'a/bx/cy/dze'],
['abcde', 'xbcdy', '/axbcd/ey'],
@ -66,7 +66,7 @@ class DifferTest extends \PHPUnit\Framework\TestCase {
];
}
public function testNonContiguousIndices() {
public function testNonContiguousIndices(): void {
$differ = new Differ(function ($a, $b) {
return $a === $b;
});

View File

@ -3,7 +3,7 @@
namespace PhpParser;
class JsonDecoderTest extends \PHPUnit\Framework\TestCase {
public function testRoundTrip() {
public function testRoundTrip(): void {
$code = <<<'PHP'
<?php
// comment
@ -23,14 +23,14 @@ PHP;
}
/** @dataProvider provideTestDecodingError */
public function testDecodingError($json, $expectedMessage) {
public function testDecodingError($json, $expectedMessage): void {
$jsonDecoder = new JsonDecoder();
$this->expectException(\RuntimeException::class);
$this->expectExceptionMessage($expectedMessage);
$jsonDecoder->decode($json);
}
public function provideTestDecodingError() {
public static function provideTestDecodingError() {
return [
['???', 'JSON decoding error: Syntax error'],
['{"nodeType":123}', 'Node type must be a string'],

View File

@ -19,7 +19,7 @@ class EmulativeTest extends LexerTest {
/**
* @dataProvider provideTestReplaceKeywords
*/
public function testReplaceKeywords(string $keyword, int $expectedToken) {
public function testReplaceKeywords(string $keyword, int $expectedToken): void {
$lexer = $this->getLexer();
$code = '<?php ' . $keyword;
$this->assertEquals([
@ -32,7 +32,7 @@ class EmulativeTest extends LexerTest {
/**
* @dataProvider provideTestReplaceKeywords
*/
public function testReplaceKeywordsUppercase(string $keyword, int $expectedToken) {
public function testReplaceKeywordsUppercase(string $keyword, int $expectedToken): void {
$lexer = $this->getLexer();
$code = '<?php ' . strtoupper($keyword);
@ -46,7 +46,7 @@ class EmulativeTest extends LexerTest {
/**
* @dataProvider provideTestReplaceKeywords
*/
public function testNoReplaceKeywordsAfterObjectOperator(string $keyword) {
public function testNoReplaceKeywordsAfterObjectOperator(string $keyword): void {
$lexer = $this->getLexer();
$code = '<?php ->' . $keyword;
@ -61,7 +61,7 @@ class EmulativeTest extends LexerTest {
/**
* @dataProvider provideTestReplaceKeywords
*/
public function testNoReplaceKeywordsAfterObjectOperatorWithSpaces(string $keyword) {
public function testNoReplaceKeywordsAfterObjectOperatorWithSpaces(string $keyword): void {
$lexer = $this->getLexer();
$code = '<?php -> ' . $keyword;
@ -77,7 +77,7 @@ class EmulativeTest extends LexerTest {
/**
* @dataProvider provideTestReplaceKeywords
*/
public function testNoReplaceKeywordsAfterNullsafeObjectOperator(string $keyword) {
public function testNoReplaceKeywordsAfterNullsafeObjectOperator(string $keyword): void {
$lexer = $this->getLexer();
$code = '<?php ?->' . $keyword;
@ -89,8 +89,11 @@ class EmulativeTest extends LexerTest {
], $lexer->tokenize($code));
}
public function provideTestReplaceKeywords() {
public static function provideTestReplaceKeywords() {
return [
// PHP 8.4
['__PROPERTY__', \T_PROPERTY_C],
// PHP 8.0
['match', \T_MATCH],
@ -115,7 +118,7 @@ class EmulativeTest extends LexerTest {
];
}
private function assertSameTokens(array $expectedTokens, array $tokens) {
private function assertSameTokens(array $expectedTokens, array $tokens): void {
$reducedTokens = [];
foreach ($tokens as $token) {
if ($token->id === 0 || $token->isIgnorable()) {
@ -129,7 +132,7 @@ class EmulativeTest extends LexerTest {
/**
* @dataProvider provideTestLexNewFeatures
*/
public function testLexNewFeatures(string $code, array $expectedTokens) {
public function testLexNewFeatures(string $code, array $expectedTokens): void {
$lexer = $this->getLexer();
$this->assertSameTokens($expectedTokens, $lexer->tokenize('<?php ' . $code));
}
@ -137,7 +140,7 @@ class EmulativeTest extends LexerTest {
/**
* @dataProvider provideTestLexNewFeatures
*/
public function testLeaveStuffAloneInStrings(string $code) {
public function testLeaveStuffAloneInStrings(string $code): void {
$stringifiedToken = '"' . addcslashes($code, '"\\') . '"';
$lexer = $this->getLexer();
@ -153,7 +156,7 @@ class EmulativeTest extends LexerTest {
/**
* @dataProvider provideTestLexNewFeatures
*/
public function testErrorAfterEmulation($code) {
public function testErrorAfterEmulation($code): void {
$errorHandler = new ErrorHandler\Collecting();
$lexer = $this->getLexer();
$lexer->tokenize('<?php ' . $code . "\0", $errorHandler);
@ -173,7 +176,7 @@ class EmulativeTest extends LexerTest {
$this->assertSame($expLine, $attrs['endLine']);
}
public function provideTestLexNewFeatures() {
public static function provideTestLexNewFeatures() {
return [
['yield from', [
[\T_YIELD_FROM, 'yield from'],
@ -390,18 +393,49 @@ class EmulativeTest extends LexerTest {
[\T_READONLY, 'readonly'],
[ord('('), '('],
]],
// PHP 8.4: Asymmetric visibility modifiers
['private(set)', [
[\T_PRIVATE_SET, 'private(set)']
]],
['PROTECTED(SET)', [
[\T_PROTECTED_SET, 'PROTECTED(SET)']
]],
['Public(Set)', [
[\T_PUBLIC_SET, 'Public(Set)']
]],
['public (set)', [
[\T_PUBLIC, 'public'],
[\ord('('), '('],
[\T_STRING, 'set'],
[\ord(')'), ')'],
]],
['->public(set)', [
[\T_OBJECT_OPERATOR, '->'],
[\T_STRING, 'public'],
[\ord('('), '('],
[\T_STRING, 'set'],
[\ord(')'), ')'],
]],
['?-> public(set)', [
[\T_NULLSAFE_OBJECT_OPERATOR, '?->'],
[\T_STRING, 'public'],
[\ord('('), '('],
[\T_STRING, 'set'],
[\ord(')'), ')'],
]],
];
}
/**
* @dataProvider provideTestTargetVersion
*/
public function testTargetVersion(string $phpVersion, string $code, array $expectedTokens) {
public function testTargetVersion(string $phpVersion, string $code, array $expectedTokens): void {
$lexer = new Emulative(PhpVersion::fromString($phpVersion));
$this->assertSameTokens($expectedTokens, $lexer->tokenize('<?php ' . $code));
}
public function provideTestTargetVersion() {
public static function provideTestTargetVersion() {
return [
['8.0', 'match', [[\T_MATCH, 'match']]],
['7.4', 'match', [[\T_STRING, 'match']]],
@ -424,6 +458,19 @@ class EmulativeTest extends LexerTest {
[\T_ENCAPSED_AND_WHITESPACE, ' baz'],
[ord('"'), '"'],
]],
['8.4', '__PROPERTY__', [[\T_PROPERTY_C, '__PROPERTY__']]],
['8.3', '__PROPERTY__', [[\T_STRING, '__PROPERTY__']]],
['8.4', '__property__', [[\T_PROPERTY_C, '__property__']]],
['8.3', '__property__', [[\T_STRING, '__property__']]],
['8.4', 'public(set)', [
[\T_PUBLIC_SET, 'public(set)'],
]],
['8.3', 'public(set)', [
[\T_PUBLIC, 'public'],
[\ord('('), '('],
[\T_STRING, 'set'],
[\ord(')'), ')']
]],
];
}
}

View File

@ -13,7 +13,7 @@ class LexerTest extends \PHPUnit\Framework\TestCase {
/**
* @dataProvider provideTestError
*/
public function testError($code, $messages) {
public function testError($code, $messages): void {
if (defined('HHVM_VERSION')) {
$this->markTestSkipped('HHVM does not throw warnings from token_get_all()');
}
@ -29,7 +29,7 @@ class LexerTest extends \PHPUnit\Framework\TestCase {
}
}
public function provideTestError() {
public static function provideTestError() {
return [
["<?php /*", ["Unterminated comment from 1:7 to 1:9"]],
["<?php /*\n", ["Unterminated comment from 1:7 to 2:1"]],
@ -45,7 +45,7 @@ class LexerTest extends \PHPUnit\Framework\TestCase {
];
}
public function testDefaultErrorHandler() {
public function testDefaultErrorHandler(): void {
$this->expectException(Error::class);
$this->expectExceptionMessage('Unterminated comment on line 1');
$lexer = $this->getLexer();
@ -55,7 +55,7 @@ class LexerTest extends \PHPUnit\Framework\TestCase {
/**
* @dataProvider provideTestLex
*/
public function testLex($code, $expectedTokens) {
public function testLex($code, $expectedTokens): void {
$lexer = $this->getLexer();
$tokens = $lexer->tokenize($code);
foreach ($tokens as $token) {
@ -70,7 +70,7 @@ class LexerTest extends \PHPUnit\Framework\TestCase {
}
}
public function provideTestLex() {
public static function provideTestLex() {
return [
// tests PHP 8 T_NAME_* emulation
[
@ -97,7 +97,7 @@ class LexerTest extends \PHPUnit\Framework\TestCase {
];
}
public function testGetTokens() {
public function testGetTokens(): void {
$code = '<?php "a";' . "\n" . '// foo' . "\n" . '// bar' . "\n\n" . '"b";';
$expectedTokens = [
new Token(T_OPEN_TAG, '<?php ', 1, 0),

View File

@ -0,0 +1,18 @@
<?php declare(strict_types=1);
namespace PhpParser;
use InvalidArgumentException;
use PHPUnit\Framework\TestCase;
class ModifiersTest extends TestCase {
public function testToString() {
$this->assertSame('public', Modifiers::toString(Modifiers::PUBLIC));
}
public function testToStringInvalid() {
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('Unknown modifier 3');
Modifiers::toString(Modifiers::PUBLIC | Modifiers::PROTECTED);
}
}

View File

@ -9,7 +9,7 @@ class NameContextTest extends \PHPUnit\Framework\TestCase {
/**
* @dataProvider provideTestGetPossibleNames
*/
public function testGetPossibleNames($type, $name, $expectedPossibleNames) {
public function testGetPossibleNames($type, $name, $expectedPossibleNames): void {
$nameContext = new NameContext(new ErrorHandler\Throwing());
$nameContext->startNamespace(new Name('NS'));
$nameContext->addAlias(new Name('Foo'), 'Foo', Use_::TYPE_NORMAL);
@ -32,7 +32,7 @@ class NameContextTest extends \PHPUnit\Framework\TestCase {
);
}
public function provideTestGetPossibleNames() {
public static function provideTestGetPossibleNames() {
return [
[Use_::TYPE_NORMAL, 'Test', ['\Test']],
[Use_::TYPE_NORMAL, 'Test\Namespaced', ['\Test\Namespaced']],

View File

@ -11,14 +11,14 @@ class CallableLikeTest extends \PHPUnit\Framework\TestCase {
/**
* @dataProvider provideTestIsFirstClassCallable
*/
public function testIsFirstClassCallable(CallLike $node, bool $isFirstClassCallable) {
public function testIsFirstClassCallable(CallLike $node, bool $isFirstClassCallable): void {
$this->assertSame($isFirstClassCallable, $node->isFirstClassCallable());
if (!$isFirstClassCallable) {
$this->assertSame($node->getRawArgs(), $node->getArgs());
}
}
public function provideTestIsFirstClassCallable() {
public static function provideTestIsFirstClassCallable() {
$normalArgs = [new Arg(new Int_(1))];
$callableArgs = [new VariadicPlaceholder()];
return [

View File

@ -3,7 +3,12 @@
namespace PhpParser\Node;
class IdentifierTest extends \PHPUnit\Framework\TestCase {
public function testToString() {
public function testConstructorThrows(): void {
self::expectException(\InvalidArgumentException::class);
new Identifier('');
}
public function testToString(): void {
$identifier = new Identifier('Foo');
$this->assertSame('Foo', (string) $identifier);
@ -12,12 +17,12 @@ class IdentifierTest extends \PHPUnit\Framework\TestCase {
}
/** @dataProvider provideTestIsSpecialClassName */
public function testIsSpecialClassName($identifier, $expected) {
public function testIsSpecialClassName($identifier, $expected): void {
$identifier = new Identifier($identifier);
$this->assertSame($expected, $identifier->isSpecialClassName());
}
public function provideTestIsSpecialClassName() {
public static function provideTestIsSpecialClassName() {
return [
['self', true],
['PARENT', true],

View File

@ -3,7 +3,7 @@
namespace PhpParser\Node;
class NameTest extends \PHPUnit\Framework\TestCase {
public function testConstruct() {
public function testConstruct(): void {
$name = new Name(['foo', 'bar']);
$this->assertSame('foo\bar', $name->name);
@ -14,7 +14,7 @@ class NameTest extends \PHPUnit\Framework\TestCase {
$this->assertSame('foo\bar', $name->name);
}
public function testGet() {
public function testGet(): void {
$name = new Name('foo');
$this->assertSame('foo', $name->getFirst());
$this->assertSame('foo', $name->getLast());
@ -26,7 +26,7 @@ class NameTest extends \PHPUnit\Framework\TestCase {
$this->assertSame(['foo', 'bar'], $name->getParts());
}
public function testToString() {
public function testToString(): void {
$name = new Name('Foo\Bar');
$this->assertSame('Foo\Bar', (string) $name);
@ -34,7 +34,7 @@ class NameTest extends \PHPUnit\Framework\TestCase {
$this->assertSame('foo\bar', $name->toLowerString());
}
public function testSlice() {
public function testSlice(): void {
$name = new Name('foo\bar\baz');
$this->assertEquals(new Name('foo\bar\baz'), $name->slice(0));
$this->assertEquals(new Name('bar\baz'), $name->slice(1));
@ -50,37 +50,37 @@ class NameTest extends \PHPUnit\Framework\TestCase {
$this->assertNull($name->slice(-2, -2));
}
public function testSliceOffsetTooLarge() {
public function testSliceOffsetTooLarge(): void {
$this->expectException(\OutOfBoundsException::class);
$this->expectExceptionMessage('Offset 4 is out of bounds');
(new Name('foo\bar\baz'))->slice(4);
}
public function testSliceOffsetTooSmall() {
public function testSliceOffsetTooSmall(): void {
$this->expectException(\OutOfBoundsException::class);
$this->expectExceptionMessage('Offset -4 is out of bounds');
(new Name('foo\bar\baz'))->slice(-4);
}
public function testSliceLengthTooLarge() {
public function testSliceLengthTooLarge(): void {
$this->expectException(\OutOfBoundsException::class);
$this->expectExceptionMessage('Length 4 is out of bounds');
(new Name('foo\bar\baz'))->slice(0, 4);
}
public function testSliceLengthTooSmall() {
public function testSliceLengthTooSmall(): void {
$this->expectException(\OutOfBoundsException::class);
$this->expectExceptionMessage('Length -4 is out of bounds');
(new Name('foo\bar\baz'))->slice(0, -4);
}
public function testSliceLengthTooLargeWithOffset() {
public function testSliceLengthTooLargeWithOffset(): void {
$this->expectException(\OutOfBoundsException::class);
$this->expectExceptionMessage('Length 3 is out of bounds');
(new Name('foo\bar\baz'))->slice(1, 3);
}
public function testConcat() {
public function testConcat(): void {
$this->assertEquals(new Name('foo\bar\baz'), Name::concat('foo', 'bar\baz'));
$this->assertEquals(
new Name\FullyQualified('foo\bar'),
@ -98,7 +98,7 @@ class NameTest extends \PHPUnit\Framework\TestCase {
$this->assertNull(Name::concat(null, null));
}
public function testNameTypes() {
public function testNameTypes(): void {
$name = new Name('foo');
$this->assertTrue($name->isUnqualified());
$this->assertFalse($name->isQualified());
@ -128,31 +128,31 @@ class NameTest extends \PHPUnit\Framework\TestCase {
$this->assertSame('namespace\foo', $name->toCodeString());
}
public function testInvalidArg() {
public function testInvalidArg(): void {
$this->expectException(\InvalidArgumentException::class);
$this->expectExceptionMessage('Expected string, array of parts or Name instance');
Name::concat('foo', new \stdClass());
}
public function testInvalidEmptyString() {
public function testInvalidEmptyString(): void {
$this->expectException(\InvalidArgumentException::class);
$this->expectExceptionMessage('Name cannot be empty');
new Name('');
}
public function testInvalidEmptyArray() {
public function testInvalidEmptyArray(): void {
$this->expectException(\InvalidArgumentException::class);
$this->expectExceptionMessage('Name cannot be empty');
new Name([]);
}
/** @dataProvider provideTestIsSpecialClassName */
public function testIsSpecialClassName($name, $expected) {
public function testIsSpecialClassName($name, $expected): void {
$name = new Name($name);
$this->assertSame($expected, $name->isSpecialClassName());
}
public function provideTestIsSpecialClassName() {
public static function provideTestIsSpecialClassName() {
return [
['self', true],
['PARENT', true],

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