Compare commits

..

55 Commits

Author SHA1 Message Date
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
163 changed files with 7506 additions and 4302 deletions

4
.gitattributes vendored
View File

@ -7,7 +7,11 @@
.editorconfig export-ignore .editorconfig export-ignore
.gitattributes export-ignore .gitattributes export-ignore
.gitignore export-ignore .gitignore export-ignore
.php-cs-fixer.dist.php export-ignore
Makefile export-ignore
CHANGELOG.md export-ignore CHANGELOG.md export-ignore
CONTRIBUTING.md export-ignore CONTRIBUTING.md export-ignore
phpstan-baseline.neon export-ignore
phpstan.neon.dist export-ignore
phpunit.xml.dist export-ignore phpunit.xml.dist export-ignore
UPGRADE-*.md export-ignore UPGRADE-*.md export-ignore

View File

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

View File

@ -1,3 +1,85 @@
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) 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 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. 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,7 @@ tools/vendor:
composer install -d tools composer install -d tools
phpstan: tools/vendor phpstan: tools/vendor
tools/vendor/bin/phpstan php tools/vendor/bin/phpstan
php-cs-fixer: tools/vendor php-cs-fixer: tools/vendor
tools/vendor/bin/php-cs-fixer fix php tools/vendor/bin/php-cs-fixer fix

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 This is a PHP parser written in PHP. Its purpose is to simplify static code analysis and
manipulation. 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). [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

@ -519,7 +519,7 @@ $newStmts = $traverser->traverse($oldStmts);
$parser = (new ParserFactory())->createForNewestSupportedVersion(); $parser = (new ParserFactory())->createForNewestSupportedVersion();
$oldStmts = $parser->parse($code); $oldStmts = $parser->parse($code);
$oldTokens = $lexer->getTokens(); $oldTokens = $parser->getTokens();
$traverser = new NodeTraverser(new NodeVisitor\CloningVisitor()); $traverser = new NodeTraverser(new NodeVisitor\CloningVisitor());
$newStmts = $traverser->traverse($oldStmts); $newStmts = $traverser->traverse($oldStmts);

View File

@ -19,7 +19,7 @@
"ext-ctype": "*" "ext-ctype": "*"
}, },
"require-dev": { "require-dev": {
"phpunit/phpunit": "^7.0 || ^8.0 || ^9.0", "phpunit/phpunit": "^9.0",
"ircmaxell/php-yacc": "^0.0.7" "ircmaxell/php-yacc": "^0.0.7"
}, },
"extra": { "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 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. 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. perfect, but works well in practice.
Finally, it should be noted that the parser aims to accept all valid code, not reject all invalid Finally, it should be noted that the parser aims to accept all valid code, not reject all invalid

View File

@ -104,7 +104,7 @@ This will result in the following output (which includes attributes):
"nodeType": "Scalar_String", "nodeType": "Scalar_String",
"attributes": { "attributes": {
"startLine": 5, "startLine": 5,
"endLine": 5 "endLine": 5,
"kind": 2, "kind": 2,
"rawValue": "\"\\n\"" "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 * `phpVersion` (defaults to 7.4) allows opting into formatting that is not supported by older PHP
versions. versions.
* `newline` (defaults to `"\n"`) can be set to `"\r\n"` in order to produce Windows newlines. * `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 * `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. a legacy option, and `phpVersion` should be used to control this behavior instead.

View File

@ -1,7 +1,7 @@
<?php declare(strict_types=1); <?php declare(strict_types=1);
$meta # $meta #
#semval($) $this->semValue #semval($) $self->semValue
#semval($,%t) $this->semValue #semval($,%t) $self->semValue
#semval(%n) $stackPos-(%l-%n) #semval(%n) $stackPos-(%l-%n)
#semval(%n,%t) $stackPos-(%l-%n) #semval(%n,%t) $stackPos-(%l-%n)
@ -97,7 +97,7 @@ class #(-p) extends \PhpParser\ParserAbstract
protected function initReduceCallbacks(): void { protected function initReduceCallbacks(): void {
$this->reduceCallbacks = [ $this->reduceCallbacks = [
#reduce #reduce
%n => function ($stackPos) { %n => static function ($self, $stackPos) {
%b %b
}, },
#noact #noact

View File

@ -81,7 +81,10 @@
%token T_USE %token T_USE
%token T_INSTEADOF %token T_INSTEADOF
%token T_GLOBAL %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_VAR
%token T_UNSET %token T_UNSET
%token T_ISSET %token T_ISSET
@ -103,6 +106,7 @@
%token T_TRAIT_C %token T_TRAIT_C
%token T_METHOD_C %token T_METHOD_C
%token T_FUNC_C %token T_FUNC_C
%token T_PROPERTY_C
%token T_LINE %token T_LINE
%token T_FILE %token T_FILE
%token T_START_HEREDOC %token T_START_HEREDOC
@ -252,7 +256,7 @@ top_statement:
$this->checkNamespace($$); } $this->checkNamespace($$); }
| T_USE use_declarations semi { $$ = Stmt\Use_[$2, Stmt\Use_::TYPE_NORMAL]; } | T_USE use_declarations semi { $$ = Stmt\Use_[$2, Stmt\Use_::TYPE_NORMAL]; }
| T_USE use_type use_declarations semi { $$ = Stmt\Use_[$3, $2]; } | T_USE use_type use_declarations semi { $$ = Stmt\Use_[$3, $2]; }
| group_use_declaration semi | group_use_declaration
| T_CONST constant_declaration_list semi { $$ = Stmt\Const_[$2]; } | T_CONST constant_declaration_list semi { $$ = Stmt\Const_[$2]; }
; ;
@ -262,9 +266,9 @@ use_type:
; ;
group_use_declaration: 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]; } { $$ = 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]; } { $$ = Stmt\GroupUse[$2, $5, Stmt\Use_::TYPE_UNKNOWN]; }
; ;
@ -460,15 +464,16 @@ block_or_error:
| error { $$ = []; } | error { $$ = []; }
; ;
identifier_maybe_readonly: fn_identifier:
identifier_not_reserved identifier_not_reserved
| T_READONLY { $$ = Node\Identifier[$1]; } | T_READONLY { $$ = Node\Identifier[$1]; }
| T_EXIT { $$ = Node\Identifier[$1]; }
; ;
function_declaration_statement: 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' => []]]; } { $$ = 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]]; } { $$ = Stmt\Function_[$4, ['byRef' => $3, 'params' => $6, 'returnType' => $8, 'stmts' => $9, 'attrGroups' => $1]]; }
; ;
@ -671,17 +676,20 @@ property_modifier:
T_PUBLIC { $$ = Modifiers::PUBLIC; } T_PUBLIC { $$ = Modifiers::PUBLIC; }
| T_PROTECTED { $$ = Modifiers::PROTECTED; } | T_PROTECTED { $$ = Modifiers::PROTECTED; }
| T_PRIVATE { $$ = Modifiers::PRIVATE; } | T_PRIVATE { $$ = Modifiers::PRIVATE; }
| T_PUBLIC_SET { $$ = Modifiers::PUBLIC_SET; }
| T_PROTECTED_SET { $$ = Modifiers::PROTECTED_SET; }
| T_PRIVATE_SET { $$ = Modifiers::PRIVATE_SET; }
| T_READONLY { $$ = Modifiers::READONLY; } | T_READONLY { $$ = Modifiers::READONLY; }
; ;
parameter: parameter:
optional_attributes optional_property_modifiers optional_type_without_static optional_attributes optional_property_modifiers optional_type_without_static
optional_arg_ref optional_ellipsis plain_variable optional_arg_ref optional_ellipsis plain_variable optional_property_hook_list
{ $$ = new Node\Param($6, null, $3, $4, $5, attributes(), $2, $1); { $$ = new Node\Param($6, null, $3, $4, $5, attributes(), $2, $1, $7);
$this->checkParam($$); } $this->checkParam($$); }
| optional_attributes optional_property_modifiers optional_type_without_static | optional_attributes optional_property_modifiers optional_type_without_static
optional_arg_ref optional_ellipsis plain_variable '=' expr optional_arg_ref optional_ellipsis plain_variable '=' expr optional_property_hook_list
{ $$ = new Node\Param($6, $8, $3, $4, $5, attributes(), $2, $1); { $$ = new Node\Param($6, $8, $3, $4, $5, attributes(), $2, $1, $9);
$this->checkParam($$); } $this->checkParam($$); }
| optional_attributes optional_property_modifiers optional_type_without_static | optional_attributes optional_property_modifiers optional_type_without_static
optional_arg_ref optional_ellipsis error optional_arg_ref optional_ellipsis error
@ -828,8 +836,12 @@ class_statement_list:
class_statement: class_statement:
optional_attributes variable_modifiers optional_type_without_static property_declaration_list semi optional_attributes variable_modifiers optional_type_without_static property_declaration_list semi
{ $$ = new Stmt\Property($2, $4, attributes(), $3, $1); { $$ = new Stmt\Property($2, $4, attributes(), $3, $1); }
$this->checkProperty($$, #2); } #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->checkPropertyHookList($6, #5); }
#endif
| optional_attributes method_modifiers T_CONST class_const_list semi | optional_attributes method_modifiers T_CONST class_const_list semi
{ $$ = new Stmt\ClassConst($4, $2, attributes(), $1); { $$ = new Stmt\ClassConst($4, $2, attributes(), $1);
$this->checkClassConst($$, #2); } $this->checkClassConst($$, #2); }
@ -901,6 +913,9 @@ member_modifier:
T_PUBLIC { $$ = Modifiers::PUBLIC; } T_PUBLIC { $$ = Modifiers::PUBLIC; }
| T_PROTECTED { $$ = Modifiers::PROTECTED; } | T_PROTECTED { $$ = Modifiers::PROTECTED; }
| T_PRIVATE { $$ = Modifiers::PRIVATE; } | 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_STATIC { $$ = Modifiers::STATIC; }
| T_ABSTRACT { $$ = Modifiers::ABSTRACT; } | T_ABSTRACT { $$ = Modifiers::ABSTRACT; }
| T_FINAL { $$ = Modifiers::FINAL; } | T_FINAL { $$ = Modifiers::FINAL; }
@ -926,6 +941,39 @@ property_declaration:
| property_decl_name '=' expr { $$ = Node\PropertyItem[$1, $3]; } | 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->checkPropertyHookList($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: expr_list_forbid_comma:
non_empty_expr_list no_comma non_empty_expr_list no_comma
; ;
@ -1030,10 +1078,8 @@ expr:
| T_OBJECT_CAST expr { $$ = Expr\Cast\Object_ [$2]; } | T_OBJECT_CAST expr { $$ = Expr\Cast\Object_ [$2]; }
| T_BOOL_CAST expr { $$ = Expr\Cast\Bool_ [$2]; } | T_BOOL_CAST expr { $$ = Expr\Cast\Bool_ [$2]; }
| T_UNSET_CAST expr { $$ = Expr\Cast\Unset_ [$2]; } | T_UNSET_CAST expr { $$ = Expr\Cast\Unset_ [$2]; }
| T_EXIT exit_expr | T_EXIT ctor_arguments
{ $attrs = attributes(); { $$ = $this->createExitExpr($1, #1, $2, attributes()); }
$attrs['kind'] = strtolower($1) === 'exit' ? Expr\Exit_::KIND_EXIT : Expr\Exit_::KIND_DIE;
$$ = new Expr\Exit_($2, $attrs); }
| '@' expr { $$ = Expr\ErrorSuppress[$2]; } | '@' expr { $$ = Expr\ErrorSuppress[$2]; }
| scalar | scalar
| '`' backticks_expr '`' { $$ = Expr\ShellExec[$2]; } | '`' backticks_expr '`' { $$ = Expr\ShellExec[$2]; }
@ -1069,12 +1115,21 @@ anonymous_class:
$this->checkClass($$[0], -1); } $this->checkClass($$[0], -1); }
; ;
new_expr: new_dereferenceable:
T_NEW class_name_reference ctor_arguments { $$ = Expr\New_[$2, $3]; } T_NEW class_name_reference argument_list { $$ = Expr\New_[$2, $3]; }
| T_NEW anonymous_class | T_NEW anonymous_class
{ list($class, $ctorArgs) = $2; $$ = Expr\New_[$class, $ctorArgs]; } { 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: lexical_vars:
/* empty */ { $$ = array(); } /* empty */ { $$ = array(); }
| T_USE '(' lexical_var_list ')' { $$ = $3; } | T_USE '(' lexical_var_list ')' { $$ = $3; }
@ -1126,12 +1181,7 @@ class_name_reference:
class_name_or_var: class_name_or_var:
class_name class_name
| fully_dereferencable | fully_dereferenceable
;
exit_expr:
/* empty */ { $$ = null; }
| '(' optional_expr ')' { $$ = $2; }
; ;
backticks_expr: backticks_expr:
@ -1156,6 +1206,7 @@ constant:
| T_METHOD_C { $$ = Scalar\MagicConst\Method[]; } | T_METHOD_C { $$ = Scalar\MagicConst\Method[]; }
| T_FUNC_C { $$ = Scalar\MagicConst\Function_[]; } | T_FUNC_C { $$ = Scalar\MagicConst\Function_[]; }
| T_NS_C { $$ = Scalar\MagicConst\Namespace_[]; } | T_NS_C { $$ = Scalar\MagicConst\Namespace_[]; }
| T_PROPERTY_C { $$ = Scalar\MagicConst\Property[]; }
; ;
class_constant: class_constant:
@ -1175,7 +1226,7 @@ array_short_syntax:
$$ = new Expr\Array_($2, $attrs); } $$ = new Expr\Array_($2, $attrs); }
; ;
dereferencable_scalar: dereferenceable_scalar:
T_ARRAY '(' array_pair_list ')' T_ARRAY '(' array_pair_list ')'
{ $attrs = attributes(); $attrs['kind'] = Expr\Array_::KIND_LONG; { $attrs = attributes(); $attrs['kind'] = Expr\Array_::KIND_LONG;
$$ = new Expr\Array_($3, $attrs); $$ = new Expr\Array_($3, $attrs);
@ -1192,7 +1243,7 @@ scalar:
T_LNUMBER T_LNUMBER
{ $$ = $this->parseLNumber($1, attributes(), $this->phpVersion->allowsInvalidOctals()); } { $$ = $this->parseLNumber($1, attributes(), $this->phpVersion->allowsInvalidOctals()); }
| T_DNUMBER { $$ = Scalar\Float_::fromString($1, attributes()); } | T_DNUMBER { $$ = Scalar\Float_::fromString($1, attributes()); }
| dereferencable_scalar | dereferenceable_scalar
| constant | constant
| class_constant | class_constant
| T_START_HEREDOC T_ENCAPSED_AND_WHITESPACE T_END_HEREDOC | T_START_HEREDOC T_ENCAPSED_AND_WHITESPACE T_END_HEREDOC
@ -1208,32 +1259,36 @@ optional_expr:
| expr | expr
; ;
fully_dereferencable: fully_dereferenceable:
variable variable
| '(' expr ')' { $$ = $2; } | '(' expr ')' { $$ = $2; }
| dereferencable_scalar | dereferenceable_scalar
| class_constant | class_constant
| new_dereferenceable
; ;
array_object_dereferencable: array_object_dereferenceable:
fully_dereferencable fully_dereferenceable
| constant | constant
; ;
callable_expr: callable_expr:
callable_variable callable_variable
| '(' expr ')' { $$ = $2; } | '(' expr ')' { $$ = $2; }
| dereferencable_scalar | dereferenceable_scalar
| new_dereferenceable
; ;
callable_variable: callable_variable:
simple_variable simple_variable
| array_object_dereferencable '[' optional_expr ']' { $$ = Expr\ArrayDimFetch[$1, $3]; } | array_object_dereferenceable '[' optional_expr ']' { $$ = Expr\ArrayDimFetch[$1, $3]; }
| array_object_dereferencable '{' expr '}' { $$ = Expr\ArrayDimFetch[$1, $3]; } #if PHP7
| array_object_dereferenceable '{' expr '}' { $$ = Expr\ArrayDimFetch[$1, $3]; }
#endif
| function_call | 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]; } { $$ = 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]; } { $$ = Expr\NullsafeMethodCall[$1, $3, $4]; }
; ;
@ -1245,9 +1300,9 @@ optional_plain_variable:
variable: variable:
callable_variable callable_variable
| static_member | static_member
| array_object_dereferencable T_OBJECT_OPERATOR property_name | array_object_dereferenceable T_OBJECT_OPERATOR property_name
{ $$ = Expr\PropertyFetch[$1, $3]; } { $$ = 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]; } { $$ = Expr\NullsafePropertyFetch[$1, $3]; }
; ;
@ -1271,7 +1326,9 @@ static_member:
new_variable: new_variable:
simple_variable simple_variable
| new_variable '[' optional_expr ']' { $$ = Expr\ArrayDimFetch[$1, $3]; } | new_variable '[' optional_expr ']' { $$ = Expr\ArrayDimFetch[$1, $3]; }
#if PHP7
| new_variable '{' expr '}' { $$ = Expr\ArrayDimFetch[$1, $3]; } | new_variable '{' expr '}' { $$ = Expr\ArrayDimFetch[$1, $3]; }
#endif
| new_variable T_OBJECT_OPERATOR property_name { $$ = Expr\PropertyFetch[$1, $3]; } | new_variable T_OBJECT_OPERATOR property_name { $$ = Expr\PropertyFetch[$1, $3]; }
| new_variable T_NULLSAFE_OBJECT_OPERATOR property_name { $$ = Expr\NullsafePropertyFetch[$1, $3]; } | new_variable T_NULLSAFE_OBJECT_OPERATOR property_name { $$ = Expr\NullsafePropertyFetch[$1, $3]; }
| class_name T_PAAMAYIM_NEKUDOTAYIM static_member_prop_name | class_name T_PAAMAYIM_NEKUDOTAYIM static_member_prop_name

View File

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

View File

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

View File

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

View File

@ -122,6 +122,28 @@ class Param implements PhpParser\Builder {
return $this; 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. * Adds an attribute group.
* *

View File

@ -23,6 +23,8 @@ class Property implements PhpParser\Builder {
protected ?Node $type = null; protected ?Node $type = null;
/** @var list<Node\AttributeGroup> */ /** @var list<Node\AttributeGroup> */
protected array $attributeGroups = []; protected array $attributeGroups = [];
/** @var list<Node\PropertyHook> */
protected array $hooks = [];
/** /**
* Creates a property builder. * Creates a property builder.
@ -88,6 +90,50 @@ class Property implements PhpParser\Builder {
return $this; 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. * Sets default value for the property.
* *
@ -142,12 +188,27 @@ class Property implements PhpParser\Builder {
return $this; 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. * Returns the built class node.
* *
* @return Stmt\Property The built property node * @return Stmt\Property The built property node
*/ */
public function getNode(): PhpParser\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( return new Stmt\Property(
$this->flags !== 0 ? $this->flags : Modifiers::PUBLIC, $this->flags !== 0 ? $this->flags : Modifiers::PUBLIC,
[ [
@ -155,7 +216,8 @@ class Property implements PhpParser\Builder {
], ],
$this->attributes, $this->attributes,
$this->type, $this->type,
$this->attributeGroups $this->attributeGroups,
$this->hooks
); );
} }
} }

View File

@ -210,7 +210,7 @@ class BuilderFactory {
/** /**
* Creates node a for a literal value. * 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 { public function val($value): Expr {
return BuilderHelpers::normalizeValue($value); return BuilderHelpers::normalizeValue($value);

View File

@ -6,6 +6,7 @@ use PhpParser\Node\ComplexType;
use PhpParser\Node\Expr; use PhpParser\Node\Expr;
use PhpParser\Node\Identifier; use PhpParser\Node\Identifier;
use PhpParser\Node\Name; use PhpParser\Node\Name;
use PhpParser\Node\Name\FullyQualified;
use PhpParser\Node\NullableType; use PhpParser\Node\NullableType;
use PhpParser\Node\Scalar; use PhpParser\Node\Scalar;
use PhpParser\Node\Stmt; use PhpParser\Node\Stmt;
@ -214,7 +215,7 @@ final class BuilderHelpers {
* Normalizes a value: Converts nulls, booleans, integers, * Normalizes a value: Converts nulls, booleans, integers,
* floats, strings and arrays into their respective nodes * 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 * @return Expr The normalized value
*/ */
@ -268,6 +269,10 @@ final class BuilderHelpers {
return new Expr\Array_($items); 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'); throw new \LogicException('Invalid value');
} }

View File

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

View File

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

View File

@ -20,9 +20,9 @@ class TokenStream {
* *
* @param Token[] $tokens Tokens in PhpToken::tokenize() format * @param Token[] $tokens Tokens in PhpToken::tokenize() format
*/ */
public function __construct(array $tokens) { public function __construct(array $tokens, int $tabWidth) {
$this->tokens = $tokens; $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 * @return int[] Token position to indentation map
*/ */
private function calcIndentMap(): array { private function calcIndentMap(int $tabWidth): array {
$indentMap = []; $indentMap = [];
$indent = 0; $indent = 0;
foreach ($this->tokens as $token) { foreach ($this->tokens as $i => $token) {
$indentMap[] = $indent; $indentMap[] = $indent;
if ($token->id === \T_WHITESPACE) { if ($token->id === \T_WHITESPACE) {
$content = $token->text; $content = $token->text;
$newlinePos = \strrpos($content, "\n"); $newlinePos = \strrpos($content, "\n");
if (false !== $newlinePos) { 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; 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\Error;
use PhpParser\ErrorHandler; use PhpParser\ErrorHandler;
use PhpParser\Lexer; use PhpParser\Lexer;
use PhpParser\Lexer\TokenEmulator\AsymmetricVisibilityTokenEmulator;
use PhpParser\Lexer\TokenEmulator\AttributeEmulator; use PhpParser\Lexer\TokenEmulator\AttributeEmulator;
use PhpParser\Lexer\TokenEmulator\EnumTokenEmulator; use PhpParser\Lexer\TokenEmulator\EnumTokenEmulator;
use PhpParser\Lexer\TokenEmulator\CoaleseEqualTokenEmulator;
use PhpParser\Lexer\TokenEmulator\ExplicitOctalEmulator; use PhpParser\Lexer\TokenEmulator\ExplicitOctalEmulator;
use PhpParser\Lexer\TokenEmulator\FlexibleDocStringEmulator;
use PhpParser\Lexer\TokenEmulator\FnTokenEmulator;
use PhpParser\Lexer\TokenEmulator\MatchTokenEmulator; use PhpParser\Lexer\TokenEmulator\MatchTokenEmulator;
use PhpParser\Lexer\TokenEmulator\NullsafeTokenEmulator; use PhpParser\Lexer\TokenEmulator\NullsafeTokenEmulator;
use PhpParser\Lexer\TokenEmulator\NumericLiteralSeparatorEmulator; use PhpParser\Lexer\TokenEmulator\PropertyTokenEmulator;
use PhpParser\Lexer\TokenEmulator\ReadonlyFunctionTokenEmulator; use PhpParser\Lexer\TokenEmulator\ReadonlyFunctionTokenEmulator;
use PhpParser\Lexer\TokenEmulator\ReadonlyTokenEmulator; use PhpParser\Lexer\TokenEmulator\ReadonlyTokenEmulator;
use PhpParser\Lexer\TokenEmulator\ReverseEmulator; use PhpParser\Lexer\TokenEmulator\ReverseEmulator;
@ -47,6 +45,8 @@ class Emulative extends Lexer {
new ReadonlyTokenEmulator(), new ReadonlyTokenEmulator(),
new ExplicitOctalEmulator(), new ExplicitOctalEmulator(),
new ReadonlyFunctionTokenEmulator(), new ReadonlyFunctionTokenEmulator(),
new PropertyTokenEmulator(),
new AsymmetricVisibilityTokenEmulator(),
]; ];
// Collect emulators that are relevant for the PHP version we're running // Collect emulators that are relevant for the PHP version we're running

View File

@ -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 */ /** @param Token[] $tokens */
protected function isKeywordContext(array $tokens, int $pos): bool { protected function isKeywordContext(array $tokens, int $pos): bool {
$previousNonSpaceToken = $this->getPreviousNonSpaceToken($tokens, $pos); $prevToken = $this->getPreviousNonSpaceToken($tokens, $pos);
return $previousNonSpaceToken === null || $previousNonSpaceToken->id !== \T_OBJECT_OPERATOR; 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 { 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 ABSTRACT = 16;
public const FINAL = 32; public const FINAL = 32;
public const READONLY = 64; 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 * @internal
*/ */
public static function verifyClassModifier(int $a, int $b): void { public static function verifyClassModifier(int $a, int $b): void {
if ($a & Modifiers::ABSTRACT && $b & Modifiers::ABSTRACT) { assert(self::isValidModifier($b));
throw new Error('Multiple abstract modifiers are not allowed'); if (($a & $b) != 0) {
} throw new Error(
'Multiple ' . self::toString($b) . ' 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 & 48 && $b & 48) { if ($a & 48 && $b & 48) {
@ -42,24 +66,16 @@ final class Modifiers {
* @internal * @internal
*/ */
public static function verifyModifier(int $a, int $b): void { 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'); throw new Error('Multiple access type modifiers are not allowed');
} }
if ($a & Modifiers::ABSTRACT && $b & Modifiers::ABSTRACT) { if (($a & $b) != 0) {
throw new Error('Multiple abstract modifiers are not allowed'); throw new Error(
} 'Multiple ' . self::toString($b) . ' 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 & 48 && $b & 48) { if ($a & 48 && $b & 48) {

View File

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

View File

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

View File

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

View File

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

View File

@ -21,6 +21,8 @@ class Param extends NodeAbstract {
public int $flags; public int $flags;
/** @var AttributeGroup[] PHP attribute groups */ /** @var AttributeGroup[] PHP attribute groups */
public array $attrGroups; public array $attrGroups;
/** @var PropertyHook[] Property hooks for promoted properties */
public array $hooks;
/** /**
* Constructs a parameter node. * Constructs a parameter node.
@ -33,13 +35,15 @@ class Param extends NodeAbstract {
* @param array<string, mixed> $attributes Additional attributes * @param array<string, mixed> $attributes Additional attributes
* @param int $flags Optional visibility flags * @param int $flags Optional visibility flags
* @param list<AttributeGroup> $attrGroups PHP attribute groups * @param list<AttributeGroup> $attrGroups PHP attribute groups
* @param PropertyHook[] $hooks Property hooks for promoted properties
*/ */
public function __construct( public function __construct(
Expr $var, ?Expr $default = null, ?Node $type = null, Expr $var, ?Expr $default = null, ?Node $type = null,
bool $byRef = false, bool $variadic = false, bool $byRef = false, bool $variadic = false,
array $attributes = [], array $attributes = [],
int $flags = 0, int $flags = 0,
array $attrGroups = [] array $attrGroups = [],
array $hooks = []
) { ) {
$this->attributes = $attributes; $this->attributes = $attributes;
$this->type = $type; $this->type = $type;
@ -49,10 +53,11 @@ class Param extends NodeAbstract {
$this->default = $default; $this->default = $default;
$this->flags = $flags; $this->flags = $flags;
$this->attrGroups = $attrGroups; $this->attrGroups = $attrGroups;
$this->hooks = $hooks;
} }
public function getSubNodeNames(): array { 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 { public function getType(): string {
@ -81,4 +86,25 @@ class Param extends NodeAbstract {
public function isReadonly(): bool { public function isReadonly(): bool {
return (bool) ($this->flags & Modifiers::READONLY); 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,78 @@
<?php declare(strict_types=1);
namespace PhpParser\Node;
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:
* '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;
}
public function getStmts(): ?array {
if ($this->body instanceof Expr) {
return [new Return_($this->body)];
}
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

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

@ -18,6 +18,8 @@ class Property extends Node\Stmt {
public ?Node $type; public ?Node $type;
/** @var Node\AttributeGroup[] PHP attribute groups */ /** @var Node\AttributeGroup[] PHP attribute groups */
public array $attrGroups; public array $attrGroups;
/** @var Node\PropertyHook[] Property hooks */
public array $hooks;
/** /**
* Constructs a class property list node. * Constructs a class property list node.
@ -27,17 +29,19 @@ class Property extends Node\Stmt {
* @param array<string, mixed> $attributes Additional attributes * @param array<string, mixed> $attributes Additional attributes
* @param null|Identifier|Name|ComplexType $type Type declaration * @param null|Identifier|Name|ComplexType $type Type declaration
* @param Node\AttributeGroup[] $attrGroups PHP attribute groups * @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->attributes = $attributes;
$this->flags = $flags; $this->flags = $flags;
$this->props = $props; $this->props = $props;
$this->type = $type; $this->type = $type;
$this->attrGroups = $attrGroups; $this->attrGroups = $attrGroups;
$this->hooks = $hooks;
} }
public function getSubNodeNames(): array { public function getSubNodeNames(): array {
return ['attrGroups', 'flags', 'type', 'props']; return ['attrGroups', 'flags', 'type', 'props', 'hooks'];
} }
/** /**
@ -76,6 +80,27 @@ class Property extends Node\Stmt {
return (bool) ($this->flags & Modifiers::READONLY); return (bool) ($this->flags & Modifiers::READONLY);
} }
/**
* 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 { public function getType(): string {
return 'Stmt_Property'; return 'Stmt_Property';
} }

View File

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

View File

@ -185,6 +185,15 @@ class NodeDumper {
if ($flags & Modifiers::READONLY) { if ($flags & Modifiers::READONLY) {
$strs[] = '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) { if ($strs) {
return implode(' | ', $strs) . ' (' . $flags . ')'; return implode(' | ', $strs) . ' (' . $flags . ')';

View File

@ -110,6 +110,12 @@ class NameResolver extends NodeVisitorAbstract {
$node->type = $this->resolveType($node->type); $node->type = $this->resolveType($node->type);
} }
$this->resolveAttrGroups($node); $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_) { } elseif ($node instanceof Stmt\Const_) {
foreach ($node->consts as $const) { foreach ($node->consts as $const) {
$this->addNamespacedName($const); $this->addNamespacedName($const);

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. * turn is based on work by Masato Bito.
*/ */
use PhpParser\Node\Arg;
use PhpParser\Node\Expr; use PhpParser\Node\Expr;
use PhpParser\Node\Expr\Array_; use PhpParser\Node\Expr\Array_;
use PhpParser\Node\Expr\Cast\Double; use PhpParser\Node\Expr\Cast\Double;
@ -14,6 +15,7 @@ use PhpParser\Node\Identifier;
use PhpParser\Node\InterpolatedStringPart; use PhpParser\Node\InterpolatedStringPart;
use PhpParser\Node\Name; use PhpParser\Node\Name;
use PhpParser\Node\Param; use PhpParser\Node\Param;
use PhpParser\Node\PropertyHook;
use PhpParser\Node\Scalar\InterpolatedString; use PhpParser\Node\Scalar\InterpolatedString;
use PhpParser\Node\Scalar\Int_; use PhpParser\Node\Scalar\Int_;
use PhpParser\Node\Scalar\String_; use PhpParser\Node\Scalar\String_;
@ -318,7 +320,7 @@ abstract class ParserAbstract implements Parser {
try { try {
$callback = $this->reduceCallbacks[$rule]; $callback = $this->reduceCallbacks[$rule];
if ($callback !== null) { if ($callback !== null) {
$callback($stackPos); $callback($this, $stackPos);
} elseif ($ruleLength > 0) { } elseif ($ruleLength > 0) {
$this->semValue = $this->semStack[$stackPos - $ruleLength + 1]; $this->semValue = $this->semStack[$stackPos - $ruleLength + 1];
} }
@ -1137,32 +1139,12 @@ abstract class ParserAbstract implements Parser {
} }
protected function checkClassConst(ClassConst $node, int $modifierPos): void { protected function checkClassConst(ClassConst $node, int $modifierPos): void {
if ($node->flags & Modifiers::STATIC) { foreach ([Modifiers::STATIC, Modifiers::ABSTRACT, Modifiers::READONLY] as $modifier) {
$this->emitError(new Error( if ($node->flags & $modifier) {
"Cannot use 'static' as constant modifier", $this->emitError(new Error(
$this->getAttributesAt($modifierPos))); "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,68 @@ abstract class ParserAbstract implements Parser {
} }
} }
/** @param PropertyHook[] $hooks */
protected function checkPropertyHookList(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)));
}
}
/** @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. * Creates the token map.
* *
@ -1190,42 +1234,23 @@ abstract class ParserAbstract implements Parser {
protected function createTokenMap(): array { protected function createTokenMap(): array {
$tokenMap = []; $tokenMap = [];
for ($i = 0; $i < 1000; ++$i) { // Single-char tokens use an identity mapping.
if ($i < 256) { for ($i = 0; $i < 256; ++$i) {
// Single-char tokens use an identity mapping. $tokenMap[$i] = $i;
$tokenMap[$i] = $i; }
} elseif (\T_DOUBLE_COLON === $i) {
// T_DOUBLE_COLON is equivalent to T_PAAMAYIM_NEKUDOTAYIM foreach ($this->symbolToName as $name) {
$tokenMap[$i] = static::T_PAAMAYIM_NEKUDOTAYIM; if ($name[0] === 'T') {
} elseif (\T_OPEN_TAG_WITH_ECHO === $i) { $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) {
// 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);
}
} }
} }
// Assign tokens for which we define compatibility constants, as token_name() does not know them. // T_OPEN_TAG_WITH_ECHO with dropped T_OPEN_TAG results in T_ECHO
$tokenMap[\T_FN] = static::T_FN; $tokenMap[\T_OPEN_TAG_WITH_ECHO] = static::T_ECHO;
$tokenMap[\T_COALESCE_EQUAL] = static::T_COALESCE_EQUAL; // T_CLOSE_TAG is equivalent to ';'
$tokenMap[\T_NAME_QUALIFIED] = static::T_NAME_QUALIFIED; $tokenMap[\T_CLOSE_TAG] = ord(';');
$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. // Now map them to the internal symbol ID.
$fullTokenMap = []; $fullTokenMap = [];
foreach ($tokenMap as $phpToken => $extSymbol) { foreach ($tokenMap as $phpToken => $extSymbol) {

View File

@ -43,7 +43,7 @@ class PhpVersion {
* if it is still under development. * if it is still under development.
*/ */
public static function getNewestSupported(): self { public static function getNewestSupported(): self {
return self::fromComponents(8, 2); return self::fromComponents(8, 4);
} }
/** /**

View File

@ -23,7 +23,8 @@ class Standard extends PrettyPrinterAbstract {
. ($node->byRef ? '&' : '') . ($node->byRef ? '&' : '')
. ($node->variadic ? '...' : '') . ($node->variadic ? '...' : '')
. $this->p($node->var) . $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 { protected function pArg(Node\Arg $node): string {
@ -125,6 +126,10 @@ class Standard extends PrettyPrinterAbstract {
return '__TRAIT__'; return '__TRAIT__';
} }
protected function pScalar_MagicConst_Property(MagicConst\Property $node): string {
return '__PROPERTY__';
}
// Scalars // Scalars
private function indentString(string $str): string { private function indentString(string $str): string {
@ -827,7 +832,8 @@ class Standard extends PrettyPrinterAbstract {
return $this->pAttrGroups($node->attrGroups) return $this->pAttrGroups($node->attrGroups)
. (0 === $node->flags ? 'var ' : $this->pModifiers($node->flags)) . (0 === $node->flags ? 'var ' : $this->pModifiers($node->flags))
. ($node->type ? $this->p($node->type) . ' ' : '') . ($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 { protected function pPropertyItem(Node\PropertyItem $node): string {
@ -835,6 +841,15 @@ class Standard extends PrettyPrinterAbstract {
. (null !== $node->default ? ' = ' . $this->p($node->default) : ''); . (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->pMaybeMultiline($node->params, $this->phpVersion->supportsTrailingCommaInParamList()) . ')' : '')
. (\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 { protected function pStmt_ClassMethod(Stmt\ClassMethod $node): string {
return $this->pAttrGroups($node->attrGroups) return $this->pAttrGroups($node->attrGroups)
. $this->pModifiers($node->flags) . $this->pModifiers($node->flags)

View File

@ -14,6 +14,7 @@ use PhpParser\Node\Expr\Cast;
use PhpParser\Node\IntersectionType; use PhpParser\Node\IntersectionType;
use PhpParser\Node\MatchArm; use PhpParser\Node\MatchArm;
use PhpParser\Node\Param; use PhpParser\Node\Param;
use PhpParser\Node\PropertyHook;
use PhpParser\Node\Scalar; use PhpParser\Node\Scalar;
use PhpParser\Node\Stmt; use PhpParser\Node\Stmt;
use PhpParser\Node\UnionType; use PhpParser\Node\UnionType;
@ -76,7 +77,7 @@ abstract class PrettyPrinterAbstract implements PrettyPrinter {
BinaryOp\BooleanAnd::class => [120, 121, 120], BinaryOp\BooleanAnd::class => [120, 121, 120],
BinaryOp\BooleanOr::class => [130, 131, 130], BinaryOp\BooleanOr::class => [130, 131, 130],
BinaryOp\Coalesce::class => [140, 140, 141], 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\Assign::class => [160, -1, -1],
Expr\AssignRef::class => [160, -1, -1], Expr\AssignRef::class => [160, -1, -1],
AssignOp\Plus::class => [160, -1, -1], AssignOp\Plus::class => [160, -1, -1],
@ -105,6 +106,15 @@ abstract class PrettyPrinterAbstract implements PrettyPrinter {
/** @var int Current indentation level. */ /** @var int Current indentation level. */
protected int $indentLevel; 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. */ /** @var string Newline style. Does not include current indentation. */
protected string $newline; protected string $newline;
/** @var string Newline including current indentation. */ /** @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 * PHP version while specifying an older target (but the result will
* of course not be compatible with the older version in that case). * 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 $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 * * bool $shortArraySyntax: Whether to use [] instead of array() as the default array
* syntax, if the node does not specify a format. Defaults to whether * syntax, if the node does not specify a format. Defaults to whether
* the phpVersion support short array syntax. * the phpVersion support short array syntax.
* *
* @param array{ * @param array{
* phpVersion?: PhpVersion, newline?: string, shortArraySyntax?: bool * phpVersion?: PhpVersion, newline?: string, indent?: string, shortArraySyntax?: bool
* } $options Dictionary of formatting options * } $options Dictionary of formatting options
*/ */
public function __construct(array $options = []) { public function __construct(array $options = []) {
@ -189,6 +201,17 @@ abstract class PrettyPrinterAbstract implements PrettyPrinter {
$options['shortArraySyntax'] ?? $this->phpVersion->supportsShortArraySyntax(); $options['shortArraySyntax'] ?? $this->phpVersion->supportsShortArraySyntax();
$this->docStringEndToken = $this->docStringEndToken =
$this->phpVersion->supportsFlexibleHeredoc() ? null : '_DOC_STRING_END_' . mt_rand(); $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 { protected function setIndentLevel(int $level): void {
$this->indentLevel = $level; $this->indentLevel = $level;
$this->nl = $this->newline . \str_repeat(' ', $level); if ($this->useTabs) {
$tabs = \intdiv($level, $this->tabWidth);
$spaces = $level % $this->tabWidth;
$this->nl = $this->newline . \str_repeat("\t", $tabs) . \str_repeat(' ', $spaces);
} else {
$this->nl = $this->newline . \str_repeat(' ', $level);
}
} }
/** /**
* Increase indentation level. * Increase indentation level.
*/ */
protected function indent(): void { protected function indent(): void {
$this->indentLevel += 4; $this->indentLevel += $this->indentWidth;
$this->nl .= ' '; $this->nl .= $this->indent;
} }
/** /**
* Decrease indentation level. * Decrease indentation level.
*/ */
protected function outdent(): void { protected function outdent(): void {
assert($this->indentLevel >= 4); assert($this->indentLevel >= $this->indentWidth);
$this->indentLevel -= 4; $this->setIndentLevel($this->indentLevel - $this->indentWidth);
$this->nl = $this->newline . str_repeat(' ', $this->indentLevel);
} }
/** /**
@ -536,7 +564,7 @@ abstract class PrettyPrinterAbstract implements PrettyPrinter {
$this->initializeModifierChangeMap(); $this->initializeModifierChangeMap();
$this->resetState(); $this->resetState();
$this->origTokens = new TokenStream($origTokens); $this->origTokens = new TokenStream($origTokens, $this->tabWidth);
$this->preprocessNodes($stmts); $this->preprocessNodes($stmts);
@ -708,7 +736,7 @@ abstract class PrettyPrinterAbstract implements PrettyPrinter {
$result .= $extraLeft; $result .= $extraLeft;
$origIndentLevel = $this->indentLevel; $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 // 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 // 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); \assert($itemStartPos >= 0 && $itemEndPos >= 0 && $itemStartPos >= $pos);
$origIndentLevel = $this->indentLevel; $origIndentLevel = $this->indentLevel;
$lastElemIndentLevel = $this->origTokens->getIndentationBefore($itemStartPos) + $indentAdjustment; $lastElemIndentLevel = max($this->origTokens->getIndentationBefore($itemStartPos) + $indentAdjustment, 0);
$this->setIndentLevel($lastElemIndentLevel); $this->setIndentLevel($lastElemIndentLevel);
$comments = $arrItem->getComments(); $comments = $arrItem->getComments();
@ -1195,6 +1223,9 @@ abstract class PrettyPrinterAbstract implements PrettyPrinter {
. ($modifiers & Modifiers::PUBLIC ? 'public ' : '') . ($modifiers & Modifiers::PUBLIC ? 'public ' : '')
. ($modifiers & Modifiers::PROTECTED ? 'protected ' : '') . ($modifiers & Modifiers::PROTECTED ? 'protected ' : '')
. ($modifiers & Modifiers::PRIVATE ? 'private ' : '') . ($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::STATIC ? 'static ' : '')
. ($modifiers & Modifiers::READONLY ? 'readonly ' : ''); . ($modifiers & Modifiers::READONLY ? 'readonly ' : '');
} }
@ -1517,6 +1548,7 @@ abstract class PrettyPrinterAbstract implements PrettyPrinter {
Stmt\UseUse::class . '->uses' => ', ', Stmt\UseUse::class . '->uses' => ', ',
MatchArm::class . '->conds' => ', ', MatchArm::class . '->conds' => ', ',
AttributeGroup::class . '->attrs' => ', ', AttributeGroup::class . '->attrs' => ', ',
PropertyHook::class . '->params' => ', ',
// statement lists // statement lists
Expr\Closure::class . '->stmts' => "\n", Expr\Closure::class . '->stmts' => "\n",
@ -1554,10 +1586,15 @@ abstract class PrettyPrinterAbstract implements PrettyPrinter {
Expr\Closure::class . '->attrGroups' => ' ', Expr\Closure::class . '->attrGroups' => ' ',
Expr\ArrowFunction::class . '->attrGroups' => ' ', Expr\ArrowFunction::class . '->attrGroups' => ' ',
Param::class . '->attrGroups' => ' ', Param::class . '->attrGroups' => ' ',
PropertyHook::class . '->attrGroups' => ' ',
Stmt\Switch_::class . '->cases' => "\n", Stmt\Switch_::class . '->cases' => "\n",
Stmt\TraitUse::class . '->adaptations' => "\n", Stmt\TraitUse::class . '->adaptations' => "\n",
Stmt\TryCatch::class . '->stmts' => "\n", Stmt\TryCatch::class . '->stmts' => "\n",
Stmt\While_::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 // dummy for top-level context
'File->stmts' => "\n", 'File->stmts' => "\n",
@ -1641,6 +1678,7 @@ abstract class PrettyPrinterAbstract implements PrettyPrinter {
Stmt\Property::class . '->flags' => ['pModifiers', \T_VARIABLE], Stmt\Property::class . '->flags' => ['pModifiers', \T_VARIABLE],
PrintableNewAnonClassNode::class . '->flags' => ['pModifiers', \T_CLASS], PrintableNewAnonClassNode::class . '->flags' => ['pModifiers', \T_CLASS],
Param::class . '->flags' => ['pModifiers', \T_VARIABLE], Param::class . '->flags' => ['pModifiers', \T_VARIABLE],
PropertyHook::class . '->flags' => ['pModifiers', \T_STRING],
Expr\Closure::class . '->static' => ['pStatic', \T_FUNCTION], Expr\Closure::class . '->static' => ['pStatic', \T_FUNCTION],
Expr\ArrowFunction::class . '->static' => ['pStatic', \T_FN], Expr\ArrowFunction::class . '->static' => ['pStatic', \T_FN],
//Stmt\TraitUseAdaptation\Alias::class . '->newModifier' => 0, // TODO //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_NOT_FOLLOWED_BY_VAR_OR_VARARG',
'T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG', 'T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG',
'T_READONLY', '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 // 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) { foreach ($compatTokens as $token) {
if (\defined($token)) { if (\defined($token)) {
$tokenId = \constant($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; $clashingToken = $usedTokenIds[$tokenId] ?? null;
if ($clashingToken !== null) { if ($clashingToken !== null) {
throw new \Error(sprintf( throw new \Error(sprintf(

View File

@ -90,6 +90,26 @@ parameters:
count: 1 count: 1
path: lib/PhpParser/Lexer/Emulative.php path: lib/PhpParser/Lexer/Emulative.php
-
message: "#^Constant T_PRIVATE_SET not found\\.$#"
count: 2
path: lib/PhpParser/Lexer/TokenEmulator/AsymmetricVisibilityTokenEmulator.php
-
message: "#^Constant T_PROTECTED_SET not found\\.$#"
count: 2
path: lib/PhpParser/Lexer/TokenEmulator/AsymmetricVisibilityTokenEmulator.php
-
message: "#^Constant T_PUBLIC_SET not found\\.$#"
count: 2
path: lib/PhpParser/Lexer/TokenEmulator/AsymmetricVisibilityTokenEmulator.php
-
message: "#^Constant T_PROPERTY_C not found\\.$#"
count: 1
path: lib/PhpParser/Lexer/TokenEmulator/PropertyTokenEmulator.php
- -
message: "#^Method PhpParser\\\\NodeDumper\\:\\:__construct\\(\\) has parameter \\$options with no value type specified in iterable type array\\.$#" message: "#^Method PhpParser\\\\NodeDumper\\:\\:__construct\\(\\) has parameter \\$options with no value type specified in iterable type array\\.$#"
count: 1 count: 1
@ -155,76 +175,11 @@ parameters:
count: 1 count: 1
path: lib/PhpParser/NodeVisitor/ParentConnectingVisitor.php path: lib/PhpParser/NodeVisitor/ParentConnectingVisitor.php
-
message: "#^Access to undefined constant static\\(PhpParser\\\\ParserAbstract\\)\\:\\:T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG\\.$#"
count: 1
path: lib/PhpParser/ParserAbstract.php
-
message: "#^Access to undefined constant static\\(PhpParser\\\\ParserAbstract\\)\\:\\:T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG\\.$#"
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\\.$#" message: "#^Access to undefined constant static\\(PhpParser\\\\ParserAbstract\\)\\:\\:T_ECHO\\.$#"
count: 1 count: 1
path: lib/PhpParser/ParserAbstract.php 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\\.$#" message: "#^Unary operation \"\\+\" on string results in an error\\.$#"
count: 1 count: 1

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -23,13 +23,13 @@ class InterfaceTest extends \PHPUnit\Framework\TestCase {
return $pp->prettyPrint([$node]); return $pp->prettyPrint([$node]);
} }
public function testEmpty() { public function testEmpty(): void {
$contract = $this->createInterfaceBuilder()->getNode(); $contract = $this->createInterfaceBuilder()->getNode();
$this->assertInstanceOf(Stmt\Interface_::class, $contract); $this->assertInstanceOf(Stmt\Interface_::class, $contract);
$this->assertEquals(new Node\Identifier('Contract'), $contract->name); $this->assertEquals(new Node\Identifier('Contract'), $contract->name);
} }
public function testExtending() { public function testExtending(): void {
$contract = $this->createInterfaceBuilder() $contract = $this->createInterfaceBuilder()
->extend('Space\Root1', 'Root2')->getNode(); ->extend('Space\Root1', 'Root2')->getNode();
$this->assertEquals( $this->assertEquals(
@ -42,13 +42,13 @@ class InterfaceTest extends \PHPUnit\Framework\TestCase {
); );
} }
public function testAddMethod() { public function testAddMethod(): void {
$method = new Stmt\ClassMethod('doSomething'); $method = new Stmt\ClassMethod('doSomething');
$contract = $this->createInterfaceBuilder()->addStmt($method)->getNode(); $contract = $this->createInterfaceBuilder()->addStmt($method)->getNode();
$this->assertSame([$method], $contract->stmts); $this->assertSame([$method], $contract->stmts);
} }
public function testAddConst() { public function testAddConst(): void {
$const = new Stmt\ClassConst([ $const = new Stmt\ClassConst([
new Node\Const_('SPEED_OF_LIGHT', new Float_(299792458.0)) 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); $this->assertSame(299792458.0, $contract->stmts[0]->consts[0]->value->value);
} }
public function testOrder() { public function testOrder(): void {
$const = new Stmt\ClassConst([ $const = new Stmt\ClassConst([
new Node\Const_('SPEED_OF_LIGHT', new Float_(299792458)) 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]); $this->assertInstanceOf(Stmt\ClassMethod::class, $contract->stmts[1]);
} }
public function testDocComment() { public function testDocComment(): void {
$node = $this->createInterfaceBuilder() $node = $this->createInterfaceBuilder()
->setDocComment('/** Test */') ->setDocComment('/** Test */')
->getNode(); ->getNode();
@ -81,7 +81,7 @@ class InterfaceTest extends \PHPUnit\Framework\TestCase {
]), $node); ]), $node);
} }
public function testAddAttribute() { public function testAddAttribute(): void {
$attribute = new Attribute( $attribute = new Attribute(
new Name('Attr'), new Name('Attr'),
[new Arg(new Int_(1), false, false, [], new Identifier('name'))] [new Arg(new Int_(1), false, false, [], new Identifier('name'))]
@ -97,13 +97,13 @@ class InterfaceTest extends \PHPUnit\Framework\TestCase {
], []), $node); ], []), $node);
} }
public function testInvalidStmtError() { public function testInvalidStmtError(): void {
$this->expectException(\LogicException::class); $this->expectException(\LogicException::class);
$this->expectExceptionMessage('Unexpected node of type "PropertyItem"'); $this->expectExceptionMessage('Unexpected node of type "PropertyItem"');
$this->createInterfaceBuilder()->addStmt(new Node\PropertyItem('invalid')); $this->createInterfaceBuilder()->addStmt(new Node\PropertyItem('invalid'));
} }
public function testFullFunctional() { public function testFullFunctional(): void {
$const = new Stmt\ClassConst([ $const = new Stmt\ClassConst([
new Node\Const_('SPEED_OF_LIGHT', new Float_(299792458)) 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); return new Method($name);
} }
public function testModifiers() { public function testModifiers(): void {
$node = $this->createMethodBuilder('test') $node = $this->createMethodBuilder('test')
->makePublic() ->makePublic()
->makeAbstract() ->makeAbstract()
@ -63,7 +63,7 @@ class MethodTest extends \PHPUnit\Framework\TestCase {
); );
} }
public function testReturnByRef() { public function testReturnByRef(): void {
$node = $this->createMethodBuilder('test') $node = $this->createMethodBuilder('test')
->makeReturnByRef() ->makeReturnByRef()
->getNode() ->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')); $param1 = new Node\Param(new Variable('test1'));
$param2 = new Node\Param(new Variable('test2')); $param2 = new Node\Param(new Variable('test2'));
$param3 = new Node\Param(new Variable('test3')); $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')); $stmt1 = new Print_(new String_('test1'));
$stmt2 = new Print_(new String_('test2')); $stmt2 = new Print_(new String_('test2'));
$stmt3 = new Print_(new String_('test3')); $stmt3 = new Print_(new String_('test3'));
@ -118,7 +118,7 @@ class MethodTest extends \PHPUnit\Framework\TestCase {
$node $node
); );
} }
public function testDocComment() { public function testDocComment(): void {
$node = $this->createMethodBuilder('test') $node = $this->createMethodBuilder('test')
->setDocComment('/** Test */') ->setDocComment('/** Test */')
->getNode(); ->getNode();
@ -128,7 +128,7 @@ class MethodTest extends \PHPUnit\Framework\TestCase {
]), $node); ]), $node);
} }
public function testAddAttribute() { public function testAddAttribute(): void {
$attribute = new Attribute( $attribute = new Attribute(
new Name('Attr'), new Name('Attr'),
[new Arg(new Int_(1), false, false, [], new Identifier('name'))] [new Arg(new Int_(1), false, false, [], new Identifier('name'))]
@ -144,7 +144,7 @@ class MethodTest extends \PHPUnit\Framework\TestCase {
], []), $node); ], []), $node);
} }
public function testReturnType() { public function testReturnType(): void {
$node = $this->createMethodBuilder('test') $node = $this->createMethodBuilder('test')
->setReturnType('bool') ->setReturnType('bool')
->getNode(); ->getNode();
@ -153,7 +153,7 @@ class MethodTest extends \PHPUnit\Framework\TestCase {
], []), $node); ], []), $node);
} }
public function testAddStmtToAbstractMethodError() { public function testAddStmtToAbstractMethodError(): void {
$this->expectException(\LogicException::class); $this->expectException(\LogicException::class);
$this->expectExceptionMessage('Cannot add statements to an abstract method'); $this->expectExceptionMessage('Cannot add statements to an abstract method');
$this->createMethodBuilder('test') $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->expectException(\LogicException::class);
$this->expectExceptionMessage('Cannot make method with statements abstract'); $this->expectExceptionMessage('Cannot make method with statements abstract');
$this->createMethodBuilder('test') $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->expectException(\LogicException::class);
$this->expectExceptionMessage('Expected parameter node, got "Name"'); $this->expectExceptionMessage('Expected parameter node, got "Name"');
$this->createMethodBuilder('test') $this->createMethodBuilder('test')

View File

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

View File

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

View File

@ -3,6 +3,7 @@
namespace PhpParser\Builder; namespace PhpParser\Builder;
use PhpParser\Comment; use PhpParser\Comment;
use PhpParser\Error;
use PhpParser\Modifiers; use PhpParser\Modifiers;
use PhpParser\Node\Arg; use PhpParser\Node\Arg;
use PhpParser\Node\Attribute; use PhpParser\Node\Attribute;
@ -10,6 +11,8 @@ use PhpParser\Node\AttributeGroup;
use PhpParser\Node\Expr; use PhpParser\Node\Expr;
use PhpParser\Node\Identifier; use PhpParser\Node\Identifier;
use PhpParser\Node\Name; use PhpParser\Node\Name;
use PhpParser\Node\PropertyHook;
use PhpParser\Node\PropertyItem;
use PhpParser\Node\Scalar; use PhpParser\Node\Scalar;
use PhpParser\Node\Scalar\Int_; use PhpParser\Node\Scalar\Int_;
use PhpParser\Node\Stmt; use PhpParser\Node\Stmt;
@ -19,7 +22,7 @@ class PropertyTest extends \PHPUnit\Framework\TestCase {
return new Property($name); return new Property($name);
} }
public function testModifiers() { public function testModifiers(): void {
$node = $this->createPropertyBuilder('test') $node = $this->createPropertyBuilder('test')
->makePrivate() ->makePrivate()
->makeStatic() ->makeStatic()
@ -29,9 +32,7 @@ class PropertyTest extends \PHPUnit\Framework\TestCase {
$this->assertEquals( $this->assertEquals(
new Stmt\Property( new Stmt\Property(
Modifiers::PRIVATE | Modifiers::STATIC, Modifiers::PRIVATE | Modifiers::STATIC,
[ [new PropertyItem('test')]
new \PhpParser\Node\PropertyItem('test')
]
), ),
$node $node
); );
@ -44,9 +45,7 @@ class PropertyTest extends \PHPUnit\Framework\TestCase {
$this->assertEquals( $this->assertEquals(
new Stmt\Property( new Stmt\Property(
Modifiers::PROTECTED, Modifiers::PROTECTED,
[ [new PropertyItem('test')]
new \PhpParser\Node\PropertyItem('test')
]
), ),
$node $node
); );
@ -59,9 +58,7 @@ class PropertyTest extends \PHPUnit\Framework\TestCase {
$this->assertEquals( $this->assertEquals(
new Stmt\Property( new Stmt\Property(
Modifiers::PUBLIC, Modifiers::PUBLIC,
[ [new PropertyItem('test')]
new \PhpParser\Node\PropertyItem('test')
]
), ),
$node $node
); );
@ -74,15 +71,40 @@ class PropertyTest extends \PHPUnit\Framework\TestCase {
$this->assertEquals( $this->assertEquals(
new Stmt\Property( new Stmt\Property(
Modifiers::READONLY, Modifiers::READONLY,
[ [new PropertyItem('test')]
new \PhpParser\Node\PropertyItem('test')
]
), ),
$node $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') $node = $this->createPropertyBuilder('test')
->setDocComment('/** Test */') ->setDocComment('/** Test */')
->getNode(); ->getNode();
@ -101,7 +123,7 @@ class PropertyTest extends \PHPUnit\Framework\TestCase {
/** /**
* @dataProvider provideTestDefaultValues * @dataProvider provideTestDefaultValues
*/ */
public function testDefaultValues($value, $expectedValueNode) { public function testDefaultValues($value, $expectedValueNode): void {
$node = $this->createPropertyBuilder('test') $node = $this->createPropertyBuilder('test')
->setDefault($value) ->setDefault($value)
->getNode() ->getNode()
@ -110,7 +132,7 @@ class PropertyTest extends \PHPUnit\Framework\TestCase {
$this->assertEquals($expectedValueNode, $node->props[0]->default); $this->assertEquals($expectedValueNode, $node->props[0]->default);
} }
public function testAddAttribute() { public function testAddAttribute(): void {
$attribute = new Attribute( $attribute = new Attribute(
new Name('Attr'), new Name('Attr'),
[new Arg(new Int_(1), false, false, [], new Identifier('name'))] [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 [ return [
[ [
null, null,

View File

@ -23,7 +23,7 @@ class TraitTest extends \PHPUnit\Framework\TestCase {
return new Trait_($class); return new Trait_($class);
} }
public function testStmtAddition() { public function testStmtAddition(): void {
$method1 = new Stmt\ClassMethod('test1'); $method1 = new Stmt\ClassMethod('test1');
$method2 = new Stmt\ClassMethod('test2'); $method2 = new Stmt\ClassMethod('test2');
$method3 = new Stmt\ClassMethod('test3'); $method3 = new Stmt\ClassMethod('test3');
@ -49,7 +49,7 @@ class TraitTest extends \PHPUnit\Framework\TestCase {
]), $trait); ]), $trait);
} }
public function testInvalidStmtError() { public function testInvalidStmtError(): void {
$this->expectException(\LogicException::class); $this->expectException(\LogicException::class);
$this->expectExceptionMessage('Unexpected node of type "Stmt_Echo"'); $this->expectExceptionMessage('Unexpected node of type "Stmt_Echo"');
$this->createTraitBuilder('Test') $this->createTraitBuilder('Test')
@ -57,7 +57,7 @@ class TraitTest extends \PHPUnit\Framework\TestCase {
; ;
} }
public function testGetMethods() { public function testGetMethods(): void {
$methods = [ $methods = [
new ClassMethod('foo'), new ClassMethod('foo'),
new ClassMethod('bar'), new ClassMethod('bar'),
@ -77,7 +77,7 @@ class TraitTest extends \PHPUnit\Framework\TestCase {
$this->assertSame($methods, $trait->getMethods()); $this->assertSame($methods, $trait->getMethods());
} }
public function testGetProperties() { public function testGetProperties(): void {
$properties = [ $properties = [
new Property(Modifiers::PUBLIC, [new PropertyItem('foo')]), new Property(Modifiers::PUBLIC, [new PropertyItem('foo')]),
new Property(Modifiers::PUBLIC, [new PropertyItem('bar')]), new Property(Modifiers::PUBLIC, [new PropertyItem('bar')]),
@ -95,7 +95,7 @@ class TraitTest extends \PHPUnit\Framework\TestCase {
$this->assertSame($properties, $trait->getProperties()); $this->assertSame($properties, $trait->getProperties());
} }
public function testAddAttribute() { public function testAddAttribute(): void {
$attribute = new Attribute( $attribute = new Attribute(
new Name('Attr'), new Name('Attr'),
[new Arg(new Int_(1), false, false, [], new Identifier('name'))] [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); return new TraitUseAdaptation($trait, $method);
} }
public function testAsMake() { public function testAsMake(): void {
$builder = $this->createTraitUseAdaptationBuilder(null, 'foo'); $builder = $this->createTraitUseAdaptationBuilder(null, 'foo');
$this->assertEquals( $this->assertEquals(
@ -36,7 +36,7 @@ class TraitUseAdaptationTest extends \PHPUnit\Framework\TestCase {
); );
} }
public function testInsteadof() { public function testInsteadof(): void {
$node = $this->createTraitUseAdaptationBuilder('SomeTrait', 'foo') $node = $this->createTraitUseAdaptationBuilder('SomeTrait', 'foo')
->insteadof('AnotherTrait') ->insteadof('AnotherTrait')
->getNode() ->getNode()
@ -52,7 +52,7 @@ class TraitUseAdaptationTest extends \PHPUnit\Framework\TestCase {
); );
} }
public function testAsOnNotAlias() { public function testAsOnNotAlias(): void {
$this->expectException(\LogicException::class); $this->expectException(\LogicException::class);
$this->expectExceptionMessage('Cannot set alias for not alias adaptation buider'); $this->expectExceptionMessage('Cannot set alias for not alias adaptation buider');
$this->createTraitUseAdaptationBuilder('Test', 'foo') $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->expectException(\LogicException::class);
$this->expectExceptionMessage('Cannot add overwritten traits for not precedence adaptation buider'); $this->expectExceptionMessage('Cannot add overwritten traits for not precedence adaptation buider');
$this->createTraitUseAdaptationBuilder('Test', 'foo') $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->expectException(\LogicException::class);
$this->expectExceptionMessage('Precedence adaptation must have trait'); $this->expectExceptionMessage('Precedence adaptation must have trait');
$this->createTraitUseAdaptationBuilder(null, 'foo') $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->expectException(\LogicException::class);
$this->expectExceptionMessage('Cannot set access modifier for not alias adaptation buider'); $this->expectExceptionMessage('Cannot set access modifier for not alias adaptation buider');
$this->createTraitUseAdaptationBuilder('Test', 'foo') $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->expectException(\LogicException::class);
$this->expectExceptionMessage('Multiple access type modifiers are not allowed'); $this->expectExceptionMessage('Multiple access type modifiers are not allowed');
$this->createTraitUseAdaptationBuilder(null, 'foo') $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->expectException(\LogicException::class);
$this->expectExceptionMessage('Type of adaptation is not defined'); $this->expectExceptionMessage('Type of adaptation is not defined');
$this->createTraitUseAdaptationBuilder(null, 'foo') $this->createTraitUseAdaptationBuilder(null, 'foo')

View File

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

View File

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

View File

@ -15,12 +15,12 @@ class BuilderFactoryTest extends \PHPUnit\Framework\TestCase {
/** /**
* @dataProvider provideTestFactory * @dataProvider provideTestFactory
*/ */
public function testFactory($methodName, $className) { public function testFactory($methodName, $className): void {
$factory = new BuilderFactory(); $factory = new BuilderFactory();
$this->assertInstanceOf($className, $factory->$methodName('test')); $this->assertInstanceOf($className, $factory->$methodName('test'));
} }
public function provideTestFactory() { public static function provideTestFactory() {
return [ return [
['namespace', Builder\Namespace_::class], ['namespace', Builder\Namespace_::class],
['class', Builder\Class_::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(); $factory = new BuilderFactory();
$this->assertInstanceOf(Builder\ClassConst::class, $factory->classConst('TEST', 1)); $this->assertInstanceOf(Builder\ClassConst::class, $factory->classConst('TEST', 1));
} }
public function testAttribute() { public function testAttribute(): void {
$factory = new BuilderFactory(); $factory = new BuilderFactory();
$this->assertEquals( $this->assertEquals(
new Attribute(new Name('AttributeName'), [new Arg( 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(), // This method is a wrapper around BuilderHelpers::normalizeValue(),
// which is already tested elsewhere // which is already tested elsewhere
$factory = new BuilderFactory(); $factory = new BuilderFactory();
@ -63,7 +63,7 @@ class BuilderFactoryTest extends \PHPUnit\Framework\TestCase {
); );
} }
public function testConcat() { public function testConcat(): void {
$factory = new BuilderFactory(); $factory = new BuilderFactory();
$varA = new Expr\Variable('a'); $varA = new Expr\Variable('a');
$varB = new Expr\Variable('b'); $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->expectException(\LogicException::class);
$this->expectExceptionMessage('Expected at least two expressions'); $this->expectExceptionMessage('Expected at least two expressions');
(new BuilderFactory())->concat("a"); (new BuilderFactory())->concat("a");
} }
public function testConcatInvalidExpr() { public function testConcatInvalidExpr(): void {
$this->expectException(\LogicException::class); $this->expectException(\LogicException::class);
$this->expectExceptionMessage('Expected string or Expr'); $this->expectExceptionMessage('Expected string or Expr');
(new BuilderFactory())->concat("a", 42); (new BuilderFactory())->concat("a", 42);
} }
public function testArgs() { public function testArgs(): void {
$factory = new BuilderFactory(); $factory = new BuilderFactory();
$unpack = new Arg(new Expr\Variable('c'), false, true); $unpack = new Arg(new Expr\Variable('c'), false, true);
$this->assertEquals( $this->assertEquals(
@ -108,7 +108,7 @@ class BuilderFactoryTest extends \PHPUnit\Framework\TestCase {
); );
} }
public function testNamedArgs() { public function testNamedArgs(): void {
$factory = new BuilderFactory(); $factory = new BuilderFactory();
$this->assertEquals( $this->assertEquals(
[ [
@ -119,7 +119,7 @@ class BuilderFactoryTest extends \PHPUnit\Framework\TestCase {
); );
} }
public function testCalls() { public function testCalls(): void {
$factory = new BuilderFactory(); $factory = new BuilderFactory();
// Simple function call // Simple function call
@ -195,7 +195,7 @@ class BuilderFactoryTest extends \PHPUnit\Framework\TestCase {
); );
} }
public function testConstFetches() { public function testConstFetches(): void {
$factory = new BuilderFactory(); $factory = new BuilderFactory();
$this->assertEquals( $this->assertEquals(
new Expr\ConstFetch(new Name('FOO')), 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(); $factory = new BuilderFactory();
$this->assertEquals( $this->assertEquals(
new Expr\Variable("foo"), new Expr\Variable("foo"),
@ -227,7 +227,7 @@ class BuilderFactoryTest extends \PHPUnit\Framework\TestCase {
); );
} }
public function testPropertyFetch() { public function testPropertyFetch(): void {
$f = new BuilderFactory(); $f = new BuilderFactory();
$this->assertEquals( $this->assertEquals(
new Expr\PropertyFetch(new Expr\Variable('foo'), 'bar'), 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->expectException(\LogicException::class);
$this->expectExceptionMessage('Expected string or instance of Node\Identifier'); $this->expectExceptionMessage('Expected string or instance of Node\Identifier');
(new BuilderFactory())->classConstFetch('Foo', new Name('foo')); (new BuilderFactory())->classConstFetch('Foo', new Name('foo'));
} }
public function testInvalidIdentifierOrExpr() { public function testInvalidIdentifierOrExpr(): void {
$this->expectException(\LogicException::class); $this->expectException(\LogicException::class);
$this->expectExceptionMessage('Expected string or instance of Node\Identifier or Node\Expr'); $this->expectExceptionMessage('Expected string or instance of Node\Identifier or Node\Expr');
(new BuilderFactory())->staticCall('Foo', new Name('bar')); (new BuilderFactory())->staticCall('Foo', new Name('bar'));
} }
public function testInvalidNameOrExpr() { public function testInvalidNameOrExpr(): void {
$this->expectException(\LogicException::class); $this->expectException(\LogicException::class);
$this->expectExceptionMessage('Name must be a string or an instance of Node\Name or Node\Expr'); $this->expectExceptionMessage('Name must be a string or an instance of Node\Name or Node\Expr');
(new BuilderFactory())->funcCall(new Node\Stmt\Return_()); (new BuilderFactory())->funcCall(new Node\Stmt\Return_());
} }
public function testInvalidVar() { public function testInvalidVar(): void {
$this->expectException(\LogicException::class); $this->expectException(\LogicException::class);
$this->expectExceptionMessage('Variable name must be string or Expr'); $this->expectExceptionMessage('Variable name must be string or Expr');
(new BuilderFactory())->var(new Node\Stmt\Return_()); (new BuilderFactory())->var(new Node\Stmt\Return_());
} }
public function testIntegration() { public function testIntegration(): void {
$factory = new BuilderFactory(); $factory = new BuilderFactory();
$node = $factory->namespace('Name\Space') $node = $factory->namespace('Name\Space')
->addStmt($factory->use('Foo\Bar\SomeOtherClass')) ->addStmt($factory->use('Foo\Bar\SomeOtherClass'))

View File

@ -4,12 +4,13 @@ namespace PhpParser;
use PhpParser\Builder\Class_; use PhpParser\Builder\Class_;
use PhpParser\Node\Identifier; use PhpParser\Node\Identifier;
use PhpParser\Node\Name\FullyQualified;
use PhpParser\Node\Scalar; use PhpParser\Node\Scalar;
use PhpParser\Node\Stmt; use PhpParser\Node\Stmt;
use PhpParser\Node\Expr; use PhpParser\Node\Expr;
class BuilderHelpersTest extends \PHPUnit\Framework\TestCase { class BuilderHelpersTest extends \PHPUnit\Framework\TestCase {
public function testNormalizeNode() { public function testNormalizeNode(): void {
$builder = new Class_('SomeClass'); $builder = new Class_('SomeClass');
$this->assertEquals($builder->getNode(), BuilderHelpers::normalizeNode($builder)); $this->assertEquals($builder->getNode(), BuilderHelpers::normalizeNode($builder));
@ -21,7 +22,7 @@ class BuilderHelpersTest extends \PHPUnit\Framework\TestCase {
BuilderHelpers::normalizeNode('test'); BuilderHelpers::normalizeNode('test');
} }
public function testNormalizeStmt() { public function testNormalizeStmt(): void {
$stmt = new Node\Stmt\Class_('Class'); $stmt = new Node\Stmt\Class_('Class');
$this->assertSame($stmt, BuilderHelpers::normalizeStmt($stmt)); $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'))); BuilderHelpers::normalizeStmt(new Node\Attribute(new Node\Name('Test')));
} }
public function testNormalizeStmtInvalidType() { public function testNormalizeStmtInvalidType(): void {
$this->expectException(\LogicException::class); $this->expectException(\LogicException::class);
$this->expectExceptionMessage('Expected node or builder object'); $this->expectExceptionMessage('Expected node or builder object');
BuilderHelpers::normalizeStmt('test'); BuilderHelpers::normalizeStmt('test');
} }
public function testNormalizeIdentifier() { public function testNormalizeIdentifier(): void {
$identifier = new Node\Identifier('fn'); $identifier = new Node\Identifier('fn');
$this->assertSame($identifier, BuilderHelpers::normalizeIdentifier($identifier)); $this->assertSame($identifier, BuilderHelpers::normalizeIdentifier($identifier));
$this->assertEquals($identifier, BuilderHelpers::normalizeIdentifier('fn')); $this->assertEquals($identifier, BuilderHelpers::normalizeIdentifier('fn'));
@ -51,7 +52,7 @@ class BuilderHelpersTest extends \PHPUnit\Framework\TestCase {
BuilderHelpers::normalizeIdentifier(1); BuilderHelpers::normalizeIdentifier(1);
} }
public function testNormalizeIdentifierOrExpr() { public function testNormalizeIdentifierOrExpr(): void {
$identifier = new Node\Identifier('fn'); $identifier = new Node\Identifier('fn');
$this->assertSame($identifier, BuilderHelpers::normalizeIdentifierOrExpr($identifier)); $this->assertSame($identifier, BuilderHelpers::normalizeIdentifierOrExpr($identifier));
@ -64,7 +65,7 @@ class BuilderHelpersTest extends \PHPUnit\Framework\TestCase {
BuilderHelpers::normalizeIdentifierOrExpr(1); BuilderHelpers::normalizeIdentifierOrExpr(1);
} }
public function testNormalizeName() { public function testNormalizeName(): void {
$name = new Node\Name('test'); $name = new Node\Name('test');
$this->assertSame($name, BuilderHelpers::normalizeName($name)); $this->assertSame($name, BuilderHelpers::normalizeName($name));
$this->assertEquals( $this->assertEquals(
@ -82,13 +83,13 @@ class BuilderHelpersTest extends \PHPUnit\Framework\TestCase {
BuilderHelpers::normalizeName(''); BuilderHelpers::normalizeName('');
} }
public function testNormalizeNameInvalidType() { public function testNormalizeNameInvalidType(): void {
$this->expectException(\LogicException::class); $this->expectException(\LogicException::class);
$this->expectExceptionMessage('Name must be a string or an instance of Node\Name'); $this->expectExceptionMessage('Name must be a string or an instance of Node\Name');
BuilderHelpers::normalizeName(1); BuilderHelpers::normalizeName(1);
} }
public function testNormalizeNameOrExpr() { public function testNormalizeNameOrExpr(): void {
$expr = new Expr\Variable('fn'); $expr = new Expr\Variable('fn');
$this->assertSame($expr, BuilderHelpers::normalizeNameOrExpr($expr)); $this->assertSame($expr, BuilderHelpers::normalizeNameOrExpr($expr));
@ -109,13 +110,13 @@ class BuilderHelpersTest extends \PHPUnit\Framework\TestCase {
BuilderHelpers::normalizeNameOrExpr(''); BuilderHelpers::normalizeNameOrExpr('');
} }
public function testNormalizeNameOrExpInvalidType() { public function testNormalizeNameOrExpInvalidType(): void {
$this->expectException(\LogicException::class); $this->expectException(\LogicException::class);
$this->expectExceptionMessage('Name must be a string or an instance of Node\Name or Node\Expr'); $this->expectExceptionMessage('Name must be a string or an instance of Node\Name or Node\Expr');
BuilderHelpers::normalizeNameOrExpr(1); BuilderHelpers::normalizeNameOrExpr(1);
} }
public function testNormalizeType() { public function testNormalizeType(): void {
$this->assertEquals(new Node\Identifier('array'), BuilderHelpers::normalizeType('array')); $this->assertEquals(new Node\Identifier('array'), BuilderHelpers::normalizeType('array'));
$this->assertEquals(new Node\Identifier('callable'), BuilderHelpers::normalizeType('callable')); $this->assertEquals(new Node\Identifier('callable'), BuilderHelpers::normalizeType('callable'));
$this->assertEquals(new Node\Identifier('string'), BuilderHelpers::normalizeType('string')); $this->assertEquals(new Node\Identifier('string'), BuilderHelpers::normalizeType('string'));
@ -156,25 +157,25 @@ class BuilderHelpersTest extends \PHPUnit\Framework\TestCase {
BuilderHelpers::normalizeType(1); BuilderHelpers::normalizeType(1);
} }
public function testNormalizeTypeNullableVoid() { public function testNormalizeTypeNullableVoid(): void {
$this->expectException(\LogicException::class); $this->expectException(\LogicException::class);
$this->expectExceptionMessage('void type cannot be nullable'); $this->expectExceptionMessage('void type cannot be nullable');
BuilderHelpers::normalizeType('?void'); BuilderHelpers::normalizeType('?void');
} }
public function testNormalizeTypeNullableMixed() { public function testNormalizeTypeNullableMixed(): void {
$this->expectException(\LogicException::class); $this->expectException(\LogicException::class);
$this->expectExceptionMessage('mixed type cannot be nullable'); $this->expectExceptionMessage('mixed type cannot be nullable');
BuilderHelpers::normalizeType('?mixed'); BuilderHelpers::normalizeType('?mixed');
} }
public function testNormalizeTypeNullableNever() { public function testNormalizeTypeNullableNever(): void {
$this->expectException(\LogicException::class); $this->expectException(\LogicException::class);
$this->expectExceptionMessage('never type cannot be nullable'); $this->expectExceptionMessage('never type cannot be nullable');
BuilderHelpers::normalizeType('?never'); BuilderHelpers::normalizeType('?never');
} }
public function testNormalizeValue() { public function testNormalizeValue(): void {
$expression = new Scalar\Int_(1); $expression = new Scalar\Int_(1);
$this->assertSame($expression, BuilderHelpers::normalizeValue($expression)); $this->assertSame($expression, BuilderHelpers::normalizeValue($expression));
@ -200,7 +201,7 @@ class BuilderHelpersTest extends \PHPUnit\Framework\TestCase {
BuilderHelpers::normalizeValue(new \stdClass()); BuilderHelpers::normalizeValue(new \stdClass());
} }
public function testNormalizeDocComment() { public function testNormalizeDocComment(): void {
$docComment = new Comment\Doc('Some doc comment'); $docComment = new Comment\Doc('Some doc comment');
$this->assertSame($docComment, BuilderHelpers::normalizeDocComment($docComment)); $this->assertSame($docComment, BuilderHelpers::normalizeDocComment($docComment));
@ -211,7 +212,7 @@ class BuilderHelpersTest extends \PHPUnit\Framework\TestCase {
BuilderHelpers::normalizeDocComment(1); BuilderHelpers::normalizeDocComment(1);
} }
public function testNormalizeAttribute() { public function testNormalizeAttribute(): void {
$attribute = new Node\Attribute(new Node\Name('Test')); $attribute = new Node\Attribute(new Node\Name('Test'));
$attributeGroup = new Node\AttributeGroup([$attribute]); $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'); $this->expectExceptionMessage('Attribute must be an instance of PhpParser\Node\Attribute or PhpParser\Node\AttributeGroup');
BuilderHelpers::normalizeAttribute('test'); 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 * @dataProvider provideTestParse
*/ */
public function testParse($name, $code, $expected, $modeLine) { public function testParse($name, $code, $expected, $modeLine): void {
$modes = $this->parseModeLine($modeLine); $modes = $this->parseModeLine($modeLine);
$parser = $this->createParser($modes['version'] ?? null); $parser = $this->createParser($modes['version'] ?? null);
list($stmts, $output) = $this->getParseOutput($parser, $code, $modes); list($stmts, $output) = $this->getParseOutput($parser, $code, $modes);
@ -50,8 +50,8 @@ class CodeParsingTest extends CodeTestAbstract {
return [$stmts, canonicalize($output)]; return [$stmts, canonicalize($output)];
} }
public function provideTestParse() { public static function provideTestParse() {
return $this->getTests(__DIR__ . '/../code/parser', 'test'); return self::getTests(__DIR__ . '/../code/parser', 'test');
} }
private function formatErrorMessage(Error $e, $code) { private function formatErrorMessage(Error $e, $code) {
@ -62,13 +62,13 @@ class CodeParsingTest extends CodeTestAbstract {
return $e->getMessage(); return $e->getMessage();
} }
private function checkAttributes($stmts) { private function checkAttributes($stmts): void {
if ($stmts === null) { if ($stmts === null) {
return; return;
} }
$traverser = new NodeTraverser(new class () extends NodeVisitorAbstract { $traverser = new NodeTraverser(new class () extends NodeVisitorAbstract {
public function enterNode(Node $node) { public function enterNode(Node $node): void {
$startLine = $node->getStartLine(); $startLine = $node->getStartLine();
$endLine = $node->getEndLine(); $endLine = $node->getEndLine();
$startFilePos = $node->getStartFilePos(); $startFilePos = $node->getStartFilePos();

View File

@ -3,7 +3,7 @@
namespace PhpParser; namespace PhpParser;
abstract class CodeTestAbstract extends \PHPUnit\Framework\TestCase { 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(); $parser = new CodeTestParser();
$allTests = []; $allTests = [];
foreach (filesInDir($directory, $fileExtension) as $fileName => $fileContents) { foreach (filesInDir($directory, $fileExtension) as $fileName => $fileContents) {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -19,7 +19,7 @@ class EmulativeTest extends LexerTest {
/** /**
* @dataProvider provideTestReplaceKeywords * @dataProvider provideTestReplaceKeywords
*/ */
public function testReplaceKeywords(string $keyword, int $expectedToken) { public function testReplaceKeywords(string $keyword, int $expectedToken): void {
$lexer = $this->getLexer(); $lexer = $this->getLexer();
$code = '<?php ' . $keyword; $code = '<?php ' . $keyword;
$this->assertEquals([ $this->assertEquals([
@ -32,7 +32,7 @@ class EmulativeTest extends LexerTest {
/** /**
* @dataProvider provideTestReplaceKeywords * @dataProvider provideTestReplaceKeywords
*/ */
public function testReplaceKeywordsUppercase(string $keyword, int $expectedToken) { public function testReplaceKeywordsUppercase(string $keyword, int $expectedToken): void {
$lexer = $this->getLexer(); $lexer = $this->getLexer();
$code = '<?php ' . strtoupper($keyword); $code = '<?php ' . strtoupper($keyword);
@ -46,7 +46,7 @@ class EmulativeTest extends LexerTest {
/** /**
* @dataProvider provideTestReplaceKeywords * @dataProvider provideTestReplaceKeywords
*/ */
public function testNoReplaceKeywordsAfterObjectOperator(string $keyword) { public function testNoReplaceKeywordsAfterObjectOperator(string $keyword): void {
$lexer = $this->getLexer(); $lexer = $this->getLexer();
$code = '<?php ->' . $keyword; $code = '<?php ->' . $keyword;
@ -61,7 +61,7 @@ class EmulativeTest extends LexerTest {
/** /**
* @dataProvider provideTestReplaceKeywords * @dataProvider provideTestReplaceKeywords
*/ */
public function testNoReplaceKeywordsAfterObjectOperatorWithSpaces(string $keyword) { public function testNoReplaceKeywordsAfterObjectOperatorWithSpaces(string $keyword): void {
$lexer = $this->getLexer(); $lexer = $this->getLexer();
$code = '<?php -> ' . $keyword; $code = '<?php -> ' . $keyword;
@ -77,7 +77,7 @@ class EmulativeTest extends LexerTest {
/** /**
* @dataProvider provideTestReplaceKeywords * @dataProvider provideTestReplaceKeywords
*/ */
public function testNoReplaceKeywordsAfterNullsafeObjectOperator(string $keyword) { public function testNoReplaceKeywordsAfterNullsafeObjectOperator(string $keyword): void {
$lexer = $this->getLexer(); $lexer = $this->getLexer();
$code = '<?php ?->' . $keyword; $code = '<?php ?->' . $keyword;
@ -89,8 +89,11 @@ class EmulativeTest extends LexerTest {
], $lexer->tokenize($code)); ], $lexer->tokenize($code));
} }
public function provideTestReplaceKeywords() { public static function provideTestReplaceKeywords() {
return [ return [
// PHP 8.4
['__PROPERTY__', \T_PROPERTY_C],
// PHP 8.0 // PHP 8.0
['match', \T_MATCH], ['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 = []; $reducedTokens = [];
foreach ($tokens as $token) { foreach ($tokens as $token) {
if ($token->id === 0 || $token->isIgnorable()) { if ($token->id === 0 || $token->isIgnorable()) {
@ -129,7 +132,7 @@ class EmulativeTest extends LexerTest {
/** /**
* @dataProvider provideTestLexNewFeatures * @dataProvider provideTestLexNewFeatures
*/ */
public function testLexNewFeatures(string $code, array $expectedTokens) { public function testLexNewFeatures(string $code, array $expectedTokens): void {
$lexer = $this->getLexer(); $lexer = $this->getLexer();
$this->assertSameTokens($expectedTokens, $lexer->tokenize('<?php ' . $code)); $this->assertSameTokens($expectedTokens, $lexer->tokenize('<?php ' . $code));
} }
@ -137,7 +140,7 @@ class EmulativeTest extends LexerTest {
/** /**
* @dataProvider provideTestLexNewFeatures * @dataProvider provideTestLexNewFeatures
*/ */
public function testLeaveStuffAloneInStrings(string $code) { public function testLeaveStuffAloneInStrings(string $code): void {
$stringifiedToken = '"' . addcslashes($code, '"\\') . '"'; $stringifiedToken = '"' . addcslashes($code, '"\\') . '"';
$lexer = $this->getLexer(); $lexer = $this->getLexer();
@ -153,7 +156,7 @@ class EmulativeTest extends LexerTest {
/** /**
* @dataProvider provideTestLexNewFeatures * @dataProvider provideTestLexNewFeatures
*/ */
public function testErrorAfterEmulation($code) { public function testErrorAfterEmulation($code): void {
$errorHandler = new ErrorHandler\Collecting(); $errorHandler = new ErrorHandler\Collecting();
$lexer = $this->getLexer(); $lexer = $this->getLexer();
$lexer->tokenize('<?php ' . $code . "\0", $errorHandler); $lexer->tokenize('<?php ' . $code . "\0", $errorHandler);
@ -173,7 +176,7 @@ class EmulativeTest extends LexerTest {
$this->assertSame($expLine, $attrs['endLine']); $this->assertSame($expLine, $attrs['endLine']);
} }
public function provideTestLexNewFeatures() { public static function provideTestLexNewFeatures() {
return [ return [
['yield from', [ ['yield from', [
[\T_YIELD_FROM, 'yield from'], [\T_YIELD_FROM, 'yield from'],
@ -390,18 +393,49 @@ class EmulativeTest extends LexerTest {
[\T_READONLY, 'readonly'], [\T_READONLY, 'readonly'],
[ord('('), '('], [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 * @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)); $lexer = new Emulative(PhpVersion::fromString($phpVersion));
$this->assertSameTokens($expectedTokens, $lexer->tokenize('<?php ' . $code)); $this->assertSameTokens($expectedTokens, $lexer->tokenize('<?php ' . $code));
} }
public function provideTestTargetVersion() { public static function provideTestTargetVersion() {
return [ return [
['8.0', 'match', [[\T_MATCH, 'match']]], ['8.0', 'match', [[\T_MATCH, 'match']]],
['7.4', 'match', [[\T_STRING, 'match']]], ['7.4', 'match', [[\T_STRING, 'match']]],
@ -424,6 +458,19 @@ class EmulativeTest extends LexerTest {
[\T_ENCAPSED_AND_WHITESPACE, ' baz'], [\T_ENCAPSED_AND_WHITESPACE, ' baz'],
[ord('"'), '"'], [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 * @dataProvider provideTestError
*/ */
public function testError($code, $messages) { public function testError($code, $messages): void {
if (defined('HHVM_VERSION')) { if (defined('HHVM_VERSION')) {
$this->markTestSkipped('HHVM does not throw warnings from token_get_all()'); $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 [ return [
["<?php /*", ["Unterminated comment from 1:7 to 1:9"]], ["<?php /*", ["Unterminated comment from 1:7 to 1:9"]],
["<?php /*\n", ["Unterminated comment from 1:7 to 2:1"]], ["<?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->expectException(Error::class);
$this->expectExceptionMessage('Unterminated comment on line 1'); $this->expectExceptionMessage('Unterminated comment on line 1');
$lexer = $this->getLexer(); $lexer = $this->getLexer();
@ -55,7 +55,7 @@ class LexerTest extends \PHPUnit\Framework\TestCase {
/** /**
* @dataProvider provideTestLex * @dataProvider provideTestLex
*/ */
public function testLex($code, $expectedTokens) { public function testLex($code, $expectedTokens): void {
$lexer = $this->getLexer(); $lexer = $this->getLexer();
$tokens = $lexer->tokenize($code); $tokens = $lexer->tokenize($code);
foreach ($tokens as $token) { foreach ($tokens as $token) {
@ -70,7 +70,7 @@ class LexerTest extends \PHPUnit\Framework\TestCase {
} }
} }
public function provideTestLex() { public static function provideTestLex() {
return [ return [
// tests PHP 8 T_NAME_* emulation // 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";'; $code = '<?php "a";' . "\n" . '// foo' . "\n" . '// bar' . "\n\n" . '"b";';
$expectedTokens = [ $expectedTokens = [
new Token(T_OPEN_TAG, '<?php ', 1, 0), 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 * @dataProvider provideTestGetPossibleNames
*/ */
public function testGetPossibleNames($type, $name, $expectedPossibleNames) { public function testGetPossibleNames($type, $name, $expectedPossibleNames): void {
$nameContext = new NameContext(new ErrorHandler\Throwing()); $nameContext = new NameContext(new ErrorHandler\Throwing());
$nameContext->startNamespace(new Name('NS')); $nameContext->startNamespace(new Name('NS'));
$nameContext->addAlias(new Name('Foo'), 'Foo', Use_::TYPE_NORMAL); $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 [ return [
[Use_::TYPE_NORMAL, 'Test', ['\Test']], [Use_::TYPE_NORMAL, 'Test', ['\Test']],
[Use_::TYPE_NORMAL, 'Test\Namespaced', ['\Test\Namespaced']], [Use_::TYPE_NORMAL, 'Test\Namespaced', ['\Test\Namespaced']],

View File

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

View File

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

View File

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

View File

@ -6,7 +6,7 @@ use PhpParser\Modifiers;
use PhpParser\Node\Expr\Variable; use PhpParser\Node\Expr\Variable;
class ParamTest extends \PHPUnit\Framework\TestCase { class ParamTest extends \PHPUnit\Framework\TestCase {
public function testNoModifiers() { public function testNoModifiers(): void {
$node = new Param(new Variable('foo')); $node = new Param(new Variable('foo'));
$this->assertFalse($node->isPromoted()); $this->assertFalse($node->isPromoted());
@ -14,19 +14,22 @@ class ParamTest extends \PHPUnit\Framework\TestCase {
$this->assertFalse($node->isProtected()); $this->assertFalse($node->isProtected());
$this->assertFalse($node->isPrivate()); $this->assertFalse($node->isPrivate());
$this->assertFalse($node->isReadonly()); $this->assertFalse($node->isReadonly());
$this->assertFalse($node->isPublicSet());
$this->assertFalse($node->isProtectedSet());
$this->assertFalse($node->isPrivateSet());
} }
/** /**
* @dataProvider provideModifiers * @dataProvider provideModifiers
*/ */
public function testModifiers(string $modifier) { public function testModifiers(string $modifier): void {
$node = new Param(new Variable('foo')); $node = new Param(new Variable('foo'));
$node->flags = constant(Modifiers::class . '::' . strtoupper($modifier)); $node->flags = constant(Modifiers::class . '::' . strtoupper($modifier));
$this->assertTrue($node->isPromoted()); $this->assertTrue($node->isPromoted());
$this->assertTrue($node->{'is' . $modifier}()); $this->assertTrue($node->{'is' . $modifier}());
} }
public function provideModifiers() { public static function provideModifiers() {
return [ return [
['public'], ['public'],
['protected'], ['protected'],
@ -34,4 +37,14 @@ class ParamTest extends \PHPUnit\Framework\TestCase {
['readonly'], ['readonly'],
]; ];
} }
public function testSetVisibility() {
$node = new Param(new Variable('foo'));
$node->flags = Modifiers::PRIVATE_SET;
$this->assertTrue($node->isPrivateSet());
$node->flags = Modifiers::PROTECTED_SET;
$this->assertTrue($node->isProtectedSet());
$node->flags = Modifiers::PUBLIC_SET;
$this->assertTrue($node->isPublicSet());
}
} }

View File

@ -7,7 +7,7 @@ use PhpParser\Node\Stmt\Echo_;
use PhpParser\ParserFactory; use PhpParser\ParserFactory;
class DNumberTest extends \PHPUnit\Framework\TestCase { class DNumberTest extends \PHPUnit\Framework\TestCase {
public function testRawValue() { public function testRawValue(): void {
$parser = (new ParserFactory())->createForNewestSupportedVersion(); $parser = (new ParserFactory())->createForNewestSupportedVersion();
$nodes = $parser->parse('<?php echo 1_234.56;'); $nodes = $parser->parse('<?php echo 1_234.56;');

View File

@ -6,11 +6,11 @@ class MagicConstTest extends \PHPUnit\Framework\TestCase {
/** /**
* @dataProvider provideTestGetName * @dataProvider provideTestGetName
*/ */
public function testGetName(MagicConst $magicConst, $name) { public function testGetName(MagicConst $magicConst, $name): void {
$this->assertSame($name, $magicConst->getName()); $this->assertSame($name, $magicConst->getName());
} }
public function provideTestGetName() { public static function provideTestGetName() {
return [ return [
[new MagicConst\Class_(), '__CLASS__'], [new MagicConst\Class_(), '__CLASS__'],
[new MagicConst\Dir(), '__DIR__'], [new MagicConst\Dir(), '__DIR__'],

View File

@ -6,7 +6,7 @@ use PhpParser\Node\Stmt\Echo_;
use PhpParser\ParserFactory; use PhpParser\ParserFactory;
class NumberTest extends \PHPUnit\Framework\TestCase { class NumberTest extends \PHPUnit\Framework\TestCase {
public function testRawValue() { public function testRawValue(): void {
$parser = (new ParserFactory())->createForNewestSupportedVersion(); $parser = (new ParserFactory())->createForNewestSupportedVersion();
$nodes = $parser->parse('<?php echo 1_234;'); $nodes = $parser->parse('<?php echo 1_234;');

View File

@ -6,7 +6,7 @@ use PhpParser\Node\Stmt\Echo_;
use PhpParser\ParserFactory; use PhpParser\ParserFactory;
class StringTest extends \PHPUnit\Framework\TestCase { class StringTest extends \PHPUnit\Framework\TestCase {
public function testRawValue() { public function testRawValue(): void {
$parser = (new ParserFactory())->createForNewestSupportedVersion(); $parser = (new ParserFactory())->createForNewestSupportedVersion();
$nodes = $parser->parse('<?php echo "sequence \x41";'); $nodes = $parser->parse('<?php echo "sequence \x41";');
@ -25,7 +25,7 @@ class StringTest extends \PHPUnit\Framework\TestCase {
/** /**
* @dataProvider provideTestParseEscapeSequences * @dataProvider provideTestParseEscapeSequences
*/ */
public function testParseEscapeSequences($expected, $string, $quote) { public function testParseEscapeSequences($expected, $string, $quote): void {
$this->assertSame( $this->assertSame(
$expected, $expected,
String_::parseEscapeSequences($string, $quote) String_::parseEscapeSequences($string, $quote)
@ -35,14 +35,14 @@ class StringTest extends \PHPUnit\Framework\TestCase {
/** /**
* @dataProvider provideTestParse * @dataProvider provideTestParse
*/ */
public function testCreate($expected, $string) { public function testCreate($expected, $string): void {
$this->assertSame( $this->assertSame(
$expected, $expected,
String_::parse($string) String_::parse($string)
); );
} }
public function provideTestParseEscapeSequences() { public static function provideTestParseEscapeSequences() {
return [ return [
['"', '\\"', '"'], ['"', '\\"', '"'],
['\\"', '\\"', '`'], ['\\"', '\\"', '`'],
@ -57,7 +57,7 @@ class StringTest extends \PHPUnit\Framework\TestCase {
]; ];
} }
public function provideTestParse() { public static function provideTestParse() {
$tests = [ $tests = [
['A', '\'A\''], ['A', '\'A\''],
['A', 'b\'A\''], ['A', 'b\'A\''],
@ -67,7 +67,7 @@ class StringTest extends \PHPUnit\Framework\TestCase {
['\'', '\'\\\'\''], ['\'', '\'\\\'\''],
]; ];
foreach ($this->provideTestParseEscapeSequences() as $i => $test) { foreach (self::provideTestParseEscapeSequences() as $i => $test) {
// skip second and third tests, they aren't for double quotes // skip second and third tests, they aren't for double quotes
if ($i !== 1 && $i !== 2) { if ($i !== 1 && $i !== 2) {
$tests[] = [$test[0], '"' . $test[1] . '"']; $tests[] = [$test[0], '"' . $test[1] . '"'];

View File

@ -8,7 +8,7 @@ class ClassConstTest extends \PHPUnit\Framework\TestCase {
/** /**
* @dataProvider provideModifiers * @dataProvider provideModifiers
*/ */
public function testModifiers($modifier) { public function testModifiers($modifier): void {
$node = new ClassConst( $node = new ClassConst(
[], // invalid [], // invalid
constant(Modifiers::class . '::' . strtoupper($modifier)) constant(Modifiers::class . '::' . strtoupper($modifier))
@ -17,7 +17,7 @@ class ClassConstTest extends \PHPUnit\Framework\TestCase {
$this->assertTrue($node->{'is' . $modifier}()); $this->assertTrue($node->{'is' . $modifier}());
} }
public function testNoModifiers() { public function testNoModifiers(): void {
$node = new ClassConst([], 0); $node = new ClassConst([], 0);
$this->assertTrue($node->isPublic()); $this->assertTrue($node->isPublic());
@ -26,7 +26,7 @@ class ClassConstTest extends \PHPUnit\Framework\TestCase {
$this->assertFalse($node->isFinal()); $this->assertFalse($node->isFinal());
} }
public function provideModifiers() { public static function provideModifiers() {
return [ return [
['public'], ['public'],
['protected'], ['protected'],

View File

@ -11,7 +11,7 @@ class ClassMethodTest extends \PHPUnit\Framework\TestCase {
/** /**
* @dataProvider provideModifiers * @dataProvider provideModifiers
*/ */
public function testModifiers($modifier) { public function testModifiers($modifier): void {
$node = new ClassMethod('foo', [ $node = new ClassMethod('foo', [
'type' => constant(Modifiers::class . '::' . strtoupper($modifier)) 'type' => constant(Modifiers::class . '::' . strtoupper($modifier))
]); ]);
@ -19,7 +19,7 @@ class ClassMethodTest extends \PHPUnit\Framework\TestCase {
$this->assertTrue($node->{'is' . $modifier}()); $this->assertTrue($node->{'is' . $modifier}());
} }
public function testNoModifiers() { public function testNoModifiers(): void {
$node = new ClassMethod('foo', ['type' => 0]); $node = new ClassMethod('foo', ['type' => 0]);
$this->assertTrue($node->isPublic()); $this->assertTrue($node->isPublic());
@ -31,7 +31,7 @@ class ClassMethodTest extends \PHPUnit\Framework\TestCase {
$this->assertFalse($node->isMagic()); $this->assertFalse($node->isMagic());
} }
public function provideModifiers() { public static function provideModifiers() {
return [ return [
['public'], ['public'],
['protected'], ['protected'],
@ -49,7 +49,7 @@ class ClassMethodTest extends \PHPUnit\Framework\TestCase {
* *
* @param string $modifier Node type modifier * @param string $modifier Node type modifier
*/ */
public function testImplicitPublic(string $modifier) { public function testImplicitPublic(string $modifier): void {
$node = new ClassMethod('foo', [ $node = new ClassMethod('foo', [
'type' => constant(Modifiers::class . '::' . strtoupper($modifier)) 'type' => constant(Modifiers::class . '::' . strtoupper($modifier))
]); ]);
@ -57,7 +57,7 @@ class ClassMethodTest extends \PHPUnit\Framework\TestCase {
$this->assertTrue($node->isPublic(), 'Node should be implicitly public'); $this->assertTrue($node->isPublic(), 'Node should be implicitly public');
} }
public function implicitPublicModifiers() { public static function implicitPublicModifiers() {
return [ return [
['abstract'], ['abstract'],
['final'], ['final'],
@ -70,12 +70,12 @@ class ClassMethodTest extends \PHPUnit\Framework\TestCase {
* *
* @param string $name Node name * @param string $name Node name
*/ */
public function testMagic(string $name) { public function testMagic(string $name): void {
$node = new ClassMethod($name); $node = new ClassMethod($name);
$this->assertTrue($node->isMagic(), 'Method should be magic'); $this->assertTrue($node->isMagic(), 'Method should be magic');
} }
public function provideMagics() { public static function provideMagics() {
return [ return [
['__construct'], ['__construct'],
['__DESTRUCT'], ['__DESTRUCT'],
@ -95,7 +95,7 @@ class ClassMethodTest extends \PHPUnit\Framework\TestCase {
]; ];
} }
public function testFunctionLike() { public function testFunctionLike(): void {
$param = new Param(new Variable('a')); $param = new Param(new Variable('a'));
$type = new Name('Foo'); $type = new Name('Foo');
$return = new Return_(new Variable('a')); $return = new Return_(new Variable('a'));

View File

@ -7,7 +7,7 @@ use PhpParser\Node\PropertyItem;
use PhpParser\Node\Scalar\String_; use PhpParser\Node\Scalar\String_;
class ClassTest extends \PHPUnit\Framework\TestCase { class ClassTest extends \PHPUnit\Framework\TestCase {
public function testIsAbstract() { public function testIsAbstract(): void {
$class = new Class_('Foo', ['type' => Modifiers::ABSTRACT]); $class = new Class_('Foo', ['type' => Modifiers::ABSTRACT]);
$this->assertTrue($class->isAbstract()); $this->assertTrue($class->isAbstract());
@ -15,7 +15,7 @@ class ClassTest extends \PHPUnit\Framework\TestCase {
$this->assertFalse($class->isAbstract()); $this->assertFalse($class->isAbstract());
} }
public function testIsFinal() { public function testIsFinal(): void {
$class = new Class_('Foo', ['type' => Modifiers::FINAL]); $class = new Class_('Foo', ['type' => Modifiers::FINAL]);
$this->assertTrue($class->isFinal()); $this->assertTrue($class->isFinal());
@ -23,7 +23,7 @@ class ClassTest extends \PHPUnit\Framework\TestCase {
$this->assertFalse($class->isFinal()); $this->assertFalse($class->isFinal());
} }
public function testGetTraitUses() { public function testGetTraitUses(): void {
$traitUses = [ $traitUses = [
new TraitUse([new Trait_('foo')]), new TraitUse([new Trait_('foo')]),
new TraitUse([new Trait_('bar')]), new TraitUse([new Trait_('bar')]),
@ -39,7 +39,7 @@ class ClassTest extends \PHPUnit\Framework\TestCase {
$this->assertSame($traitUses, $class->getTraitUses()); $this->assertSame($traitUses, $class->getTraitUses());
} }
public function testGetMethods() { public function testGetMethods(): void {
$methods = [ $methods = [
new ClassMethod('foo'), new ClassMethod('foo'),
new ClassMethod('bar'), new ClassMethod('bar'),
@ -59,7 +59,7 @@ class ClassTest extends \PHPUnit\Framework\TestCase {
$this->assertSame($methods, $class->getMethods()); $this->assertSame($methods, $class->getMethods());
} }
public function testGetConstants() { public function testGetConstants(): void {
$constants = [ $constants = [
new ClassConst([new \PhpParser\Node\Const_('foo', new String_('foo_value'))]), new ClassConst([new \PhpParser\Node\Const_('foo', new String_('foo_value'))]),
new ClassConst([new \PhpParser\Node\Const_('bar', new String_('bar_value'))]), new ClassConst([new \PhpParser\Node\Const_('bar', new String_('bar_value'))]),
@ -76,7 +76,7 @@ class ClassTest extends \PHPUnit\Framework\TestCase {
$this->assertSame($constants, $class->getConstants()); $this->assertSame($constants, $class->getConstants());
} }
public function testGetProperties() { public function testGetProperties(): void {
$properties = [ $properties = [
new Property(Modifiers::PUBLIC, [new PropertyItem('foo')]), new Property(Modifiers::PUBLIC, [new PropertyItem('foo')]),
new Property(Modifiers::PUBLIC, [new PropertyItem('bar')]), new Property(Modifiers::PUBLIC, [new PropertyItem('bar')]),
@ -94,7 +94,7 @@ class ClassTest extends \PHPUnit\Framework\TestCase {
$this->assertSame($properties, $class->getProperties()); $this->assertSame($properties, $class->getProperties());
} }
public function testGetProperty() { public function testGetProperty(): void {
$properties = [ $properties = [
$fooProp = new Property(Modifiers::PUBLIC, [new PropertyItem('foo1')]), $fooProp = new Property(Modifiers::PUBLIC, [new PropertyItem('foo1')]),
$barProp = new Property(Modifiers::PUBLIC, [new PropertyItem('BAR1')]), $barProp = new Property(Modifiers::PUBLIC, [new PropertyItem('BAR1')]),
@ -119,7 +119,7 @@ class ClassTest extends \PHPUnit\Framework\TestCase {
$this->assertNull($class->getProperty('nonExisting')); $this->assertNull($class->getProperty('nonExisting'));
} }
public function testGetMethod() { public function testGetMethod(): void {
$methodConstruct = new ClassMethod('__CONSTRUCT'); $methodConstruct = new ClassMethod('__CONSTRUCT');
$methodTest = new ClassMethod('test'); $methodTest = new ClassMethod('test');
$class = new Class_('Foo', [ $class = new Class_('Foo', [

View File

@ -6,7 +6,7 @@ use PhpParser\Node;
use PhpParser\Node\Scalar\String_; use PhpParser\Node\Scalar\String_;
class InterfaceTest extends \PHPUnit\Framework\TestCase { class InterfaceTest extends \PHPUnit\Framework\TestCase {
public function testGetMethods() { public function testGetMethods(): void {
$methods = [ $methods = [
new ClassMethod('foo'), new ClassMethod('foo'),
new ClassMethod('bar'), new ClassMethod('bar'),
@ -24,7 +24,7 @@ class InterfaceTest extends \PHPUnit\Framework\TestCase {
$this->assertSame($methods, $interface->getMethods()); $this->assertSame($methods, $interface->getMethods());
} }
public function testGetConstants() { public function testGetConstants(): void {
$constants = [ $constants = [
new ClassConst([new \PhpParser\Node\Const_('foo', new String_('foo_value'))]), new ClassConst([new \PhpParser\Node\Const_('foo', new String_('foo_value'))]),
new ClassConst([new \PhpParser\Node\Const_('bar', new String_('bar_value'))]), new ClassConst([new \PhpParser\Node\Const_('bar', new String_('bar_value'))]),

View File

@ -8,7 +8,7 @@ class PropertyTest extends \PHPUnit\Framework\TestCase {
/** /**
* @dataProvider provideModifiers * @dataProvider provideModifiers
*/ */
public function testModifiers($modifier) { public function testModifiers($modifier): void {
$node = new Property( $node = new Property(
constant(Modifiers::class . '::' . strtoupper($modifier)), constant(Modifiers::class . '::' . strtoupper($modifier)),
[] // invalid [] // invalid
@ -17,7 +17,7 @@ class PropertyTest extends \PHPUnit\Framework\TestCase {
$this->assertTrue($node->{'is' . $modifier}()); $this->assertTrue($node->{'is' . $modifier}());
} }
public function testNoModifiers() { public function testNoModifiers(): void {
$node = new Property(0, []); $node = new Property(0, []);
$this->assertTrue($node->isPublic()); $this->assertTrue($node->isPublic());
@ -25,9 +25,12 @@ class PropertyTest extends \PHPUnit\Framework\TestCase {
$this->assertFalse($node->isPrivate()); $this->assertFalse($node->isPrivate());
$this->assertFalse($node->isStatic()); $this->assertFalse($node->isStatic());
$this->assertFalse($node->isReadonly()); $this->assertFalse($node->isReadonly());
$this->assertFalse($node->isPublicSet());
$this->assertFalse($node->isProtectedSet());
$this->assertFalse($node->isPrivateSet());
} }
public function testStaticImplicitlyPublic() { public function testStaticImplicitlyPublic(): void {
$node = new Property(Modifiers::STATIC, []); $node = new Property(Modifiers::STATIC, []);
$this->assertTrue($node->isPublic()); $this->assertTrue($node->isPublic());
$this->assertFalse($node->isProtected()); $this->assertFalse($node->isProtected());
@ -36,7 +39,7 @@ class PropertyTest extends \PHPUnit\Framework\TestCase {
$this->assertFalse($node->isReadonly()); $this->assertFalse($node->isReadonly());
} }
public function provideModifiers() { public static function provideModifiers() {
return [ return [
['public'], ['public'],
['protected'], ['protected'],
@ -45,4 +48,13 @@ class PropertyTest extends \PHPUnit\Framework\TestCase {
['readonly'], ['readonly'],
]; ];
} }
public function testSetVisibility() {
$node = new Property(Modifiers::PRIVATE_SET, []);
$this->assertTrue($node->isPrivateSet());
$node = new Property(Modifiers::PROTECTED_SET, []);
$this->assertTrue($node->isProtectedSet());
$node = new Property(Modifiers::PUBLIC_SET, []);
$this->assertTrue($node->isPublicSet());
}
} }

View File

@ -25,7 +25,7 @@ class DummyNode extends NodeAbstract {
} }
class NodeAbstractTest extends \PHPUnit\Framework\TestCase { class NodeAbstractTest extends \PHPUnit\Framework\TestCase {
public function provideNodes() { public static function provideNodes() {
$attributes = [ $attributes = [
'startLine' => 10, 'startLine' => 10,
'endLine' => 11, 'endLine' => 11,
@ -75,7 +75,7 @@ class NodeAbstractTest extends \PHPUnit\Framework\TestCase {
/** /**
* @dataProvider provideNodes * @dataProvider provideNodes
*/ */
public function testGetDocComment(array $attributes, Node $node) { public function testGetDocComment(array $attributes, Node $node): void {
$this->assertSame('/** doc comment */', $node->getDocComment()->getText()); $this->assertSame('/** doc comment */', $node->getDocComment()->getText());
$comments = $node->getComments(); $comments = $node->getComments();
@ -88,7 +88,7 @@ class NodeAbstractTest extends \PHPUnit\Framework\TestCase {
$this->assertNull($node->getDocComment()); $this->assertNull($node->getDocComment());
} }
public function testSetDocComment() { public function testSetDocComment(): void {
$node = new DummyNode(null, null, null, []); $node = new DummyNode(null, null, null, []);
// Add doc comment to node without comments // Add doc comment to node without comments
@ -119,7 +119,7 @@ class NodeAbstractTest extends \PHPUnit\Framework\TestCase {
/** /**
* @dataProvider provideNodes * @dataProvider provideNodes
*/ */
public function testChange(array $attributes, DummyNode $node) { public function testChange(array $attributes, DummyNode $node): void {
// direct modification // direct modification
$node->subNode1 = 'newValue'; $node->subNode1 = 'newValue';
$this->assertSame('newValue', $node->subNode1); $this->assertSame('newValue', $node->subNode1);
@ -137,7 +137,7 @@ class NodeAbstractTest extends \PHPUnit\Framework\TestCase {
/** /**
* @dataProvider provideNodes * @dataProvider provideNodes
*/ */
public function testIteration(array $attributes, Node $node) { public function testIteration(array $attributes, Node $node): void {
// Iteration is simple object iteration over properties, // Iteration is simple object iteration over properties,
// not over subnodes // not over subnodes
$i = 0; $i = 0;
@ -159,7 +159,7 @@ class NodeAbstractTest extends \PHPUnit\Framework\TestCase {
$this->assertSame(3, $i); $this->assertSame(3, $i);
} }
public function testAttributes() { public function testAttributes(): void {
/** @var $node Node */ /** @var $node Node */
$node = $this->getMockForAbstractClass(NodeAbstract::class); $node = $this->getMockForAbstractClass(NodeAbstract::class);
@ -201,7 +201,7 @@ class NodeAbstractTest extends \PHPUnit\Framework\TestCase {
); );
} }
public function testJsonSerialization() { public function testJsonSerialization(): void {
$code = <<<'PHP' $code = <<<'PHP'
<?php <?php
// comment // comment
@ -261,6 +261,7 @@ PHP;
}, },
"flags": 0, "flags": 0,
"attrGroups": [], "attrGroups": [],
"hooks": [],
"attributes": { "attributes": {
"startLine": 4, "startLine": 4,
"startTokenPos": 9, "startTokenPos": 9,
@ -302,6 +303,7 @@ PHP;
}, },
"flags": 0, "flags": 0,
"attrGroups": [], "attrGroups": [],
"hooks": [],
"attributes": { "attributes": {
"startLine": 4, "startLine": 4,
"startTokenPos": 17, "startTokenPos": 17,
@ -464,7 +466,8 @@ JSON;
"value": 0 "value": 0
}, },
"flags": 0, "flags": 0,
"attrGroups": [] "attrGroups": [],
"hooks": []
}, },
{ {
"nodeType": "Param", "nodeType": "Param",
@ -505,7 +508,8 @@ JSON;
"value": 1 "value": 1
}, },
"flags": 0, "flags": 0,
"attrGroups": [] "attrGroups": [],
"hooks": []
} }
], ],
"returnType": null, "returnType": null,

View File

@ -10,13 +10,13 @@ class NodeDumperTest extends \PHPUnit\Framework\TestCase {
/** /**
* @dataProvider provideTestDump * @dataProvider provideTestDump
*/ */
public function testDump($node, $dump) { public function testDump($node, $dump): void {
$dumper = new NodeDumper(); $dumper = new NodeDumper();
$this->assertSame($this->canonicalize($dump), $this->canonicalize($dumper->dump($node))); $this->assertSame($this->canonicalize($dump), $this->canonicalize($dumper->dump($node)));
} }
public function provideTestDump() { public static function provideTestDump() {
return [ return [
[ [
[], [],
@ -57,7 +57,7 @@ class NodeDumperTest extends \PHPUnit\Framework\TestCase {
]; ];
} }
public function testDumpWithPositions() { public function testDumpWithPositions(): void {
$parser = (new ParserFactory())->createForHostVersion(); $parser = (new ParserFactory())->createForHostVersion();
$dumper = new NodeDumper(['dumpPositions' => true]); $dumper = new NodeDumper(['dumpPositions' => true]);
@ -90,7 +90,7 @@ OUT;
$this->assertSame($this->canonicalize($expected), $this->canonicalize($dump)); $this->assertSame($this->canonicalize($expected), $this->canonicalize($dump));
} }
public function testError() { public function testError(): void {
$this->expectException(\InvalidArgumentException::class); $this->expectException(\InvalidArgumentException::class);
$this->expectExceptionMessage('Can only dump nodes and arrays.'); $this->expectExceptionMessage('Can only dump nodes and arrays.');
$dumper = new NodeDumper(); $dumper = new NodeDumper();

View File

@ -14,7 +14,7 @@ class NodeFinderTest extends \PHPUnit\Framework\TestCase {
return [$stmts, $vars]; return [$stmts, $vars];
} }
public function testFind() { public function testFind(): void {
$finder = new NodeFinder(); $finder = new NodeFinder();
list($stmts, $vars) = $this->getStmtsAndVars(); list($stmts, $vars) = $this->getStmtsAndVars();
$varFilter = function (Node $node) { $varFilter = function (Node $node) {
@ -29,7 +29,7 @@ class NodeFinderTest extends \PHPUnit\Framework\TestCase {
$this->assertSame([], $finder->find($stmts, $noneFilter)); $this->assertSame([], $finder->find($stmts, $noneFilter));
} }
public function testFindInstanceOf() { public function testFindInstanceOf(): void {
$finder = new NodeFinder(); $finder = new NodeFinder();
list($stmts, $vars) = $this->getStmtsAndVars(); list($stmts, $vars) = $this->getStmtsAndVars();
$this->assertSame($vars, $finder->findInstanceOf($stmts, Expr\Variable::class)); $this->assertSame($vars, $finder->findInstanceOf($stmts, Expr\Variable::class));
@ -37,7 +37,7 @@ class NodeFinderTest extends \PHPUnit\Framework\TestCase {
$this->assertSame([], $finder->findInstanceOf($stmts, Expr\BinaryOp\Mul::class)); $this->assertSame([], $finder->findInstanceOf($stmts, Expr\BinaryOp\Mul::class));
} }
public function testFindFirst() { public function testFindFirst(): void {
$finder = new NodeFinder(); $finder = new NodeFinder();
list($stmts, $vars) = $this->getStmtsAndVars(); list($stmts, $vars) = $this->getStmtsAndVars();
$varFilter = function (Node $node) { $varFilter = function (Node $node) {
@ -52,7 +52,7 @@ class NodeFinderTest extends \PHPUnit\Framework\TestCase {
$this->assertNull($finder->findFirst($stmts, $noneFilter)); $this->assertNull($finder->findFirst($stmts, $noneFilter));
} }
public function testFindFirstInstanceOf() { public function testFindFirstInstanceOf(): void {
$finder = new NodeFinder(); $finder = new NodeFinder();
list($stmts, $vars) = $this->getStmtsAndVars(); list($stmts, $vars) = $this->getStmtsAndVars();
$this->assertSame($vars[0], $finder->findFirstInstanceOf($stmts, Expr\Variable::class)); $this->assertSame($vars[0], $finder->findFirstInstanceOf($stmts, Expr\Variable::class));

View File

@ -9,7 +9,7 @@ use PhpParser\Node\Stmt\Else_;
use PhpParser\Node\Stmt\If_; use PhpParser\Node\Stmt\If_;
class NodeTraverserTest extends \PHPUnit\Framework\TestCase { class NodeTraverserTest extends \PHPUnit\Framework\TestCase {
public function testNonModifying() { public function testNonModifying(): void {
$str1Node = new String_('Foo'); $str1Node = new String_('Foo');
$str2Node = new String_('Bar'); $str2Node = new String_('Bar');
$echoNode = new Node\Stmt\Echo_([$str1Node, $str2Node]); $echoNode = new Node\Stmt\Echo_([$str1Node, $str2Node]);
@ -32,7 +32,7 @@ class NodeTraverserTest extends \PHPUnit\Framework\TestCase {
], $visitor->trace); ], $visitor->trace);
} }
public function testModifying() { public function testModifying(): void {
$str1Node = new String_('Foo'); $str1Node = new String_('Foo');
$str2Node = new String_('Bar'); $str2Node = new String_('Bar');
$printNode = new Expr\Print_($str1Node); $printNode = new Expr\Print_($str1Node);
@ -75,7 +75,7 @@ class NodeTraverserTest extends \PHPUnit\Framework\TestCase {
], $visitor3->trace); ], $visitor3->trace);
} }
public function testRemoveFromLeave() { public function testRemoveFromLeave(): void {
$str1Node = new String_('Foo'); $str1Node = new String_('Foo');
$str2Node = new String_('Bar'); $str2Node = new String_('Bar');
@ -99,7 +99,7 @@ class NodeTraverserTest extends \PHPUnit\Framework\TestCase {
], $visitor2->trace); ], $visitor2->trace);
} }
public function testRemoveFromEnter() { public function testRemoveFromEnter(): void {
$str1Node = new String_('Foo'); $str1Node = new String_('Foo');
$str2Node = new String_('Bar'); $str2Node = new String_('Bar');
@ -122,7 +122,7 @@ class NodeTraverserTest extends \PHPUnit\Framework\TestCase {
], $visitor2->trace); ], $visitor2->trace);
} }
public function testReturnArrayFromEnter() { public function testReturnArrayFromEnter(): void {
$str1Node = new String_('Str1'); $str1Node = new String_('Str1');
$str2Node = new String_('Str2'); $str2Node = new String_('Str2');
$str3Node = new String_('Str3'); $str3Node = new String_('Str3');
@ -147,7 +147,7 @@ class NodeTraverserTest extends \PHPUnit\Framework\TestCase {
], $visitor2->trace); ], $visitor2->trace);
} }
public function testMerge() { public function testMerge(): void {
$strStart = new String_('Start'); $strStart = new String_('Start');
$strMiddle = new String_('End'); $strMiddle = new String_('End');
$strEnd = new String_('Middle'); $strEnd = new String_('Middle');
@ -167,7 +167,7 @@ class NodeTraverserTest extends \PHPUnit\Framework\TestCase {
); );
} }
public function testInvalidDeepArray() { public function testInvalidDeepArray(): void {
$this->expectException(\LogicException::class); $this->expectException(\LogicException::class);
$this->expectExceptionMessage('Invalid node structure: Contains nested arrays'); $this->expectExceptionMessage('Invalid node structure: Contains nested arrays');
$strNode = new String_('Foo'); $strNode = new String_('Foo');
@ -177,7 +177,7 @@ class NodeTraverserTest extends \PHPUnit\Framework\TestCase {
$this->assertEquals($stmts, $traverser->traverse($stmts)); $this->assertEquals($stmts, $traverser->traverse($stmts));
} }
public function testDontTraverseChildren() { public function testDontTraverseChildren(): void {
$strNode = new String_('str'); $strNode = new String_('str');
$printNode = new Expr\Print_($strNode); $printNode = new Expr\Print_($strNode);
$varNode = new Expr\Variable('foo'); $varNode = new Expr\Variable('foo');
@ -212,7 +212,7 @@ class NodeTraverserTest extends \PHPUnit\Framework\TestCase {
$this->assertEquals($expectedTrace, $visitor2->trace); $this->assertEquals($expectedTrace, $visitor2->trace);
} }
public function testDontTraverseCurrentAndChildren() { public function testDontTraverseCurrentAndChildren(): void {
// print 'str'; -($foo * $foo); // print 'str'; -($foo * $foo);
$strNode = new String_('str'); $strNode = new String_('str');
$printNode = new Expr\Print_($strNode); $printNode = new Expr\Print_($strNode);
@ -254,7 +254,7 @@ class NodeTraverserTest extends \PHPUnit\Framework\TestCase {
], $visitor2->trace); ], $visitor2->trace);
} }
public function testStopTraversal() { public function testStopTraversal(): void {
$varNode1 = new Expr\Variable('a'); $varNode1 = new Expr\Variable('a');
$varNode2 = new Expr\Variable('b'); $varNode2 = new Expr\Variable('b');
$varNode3 = new Expr\Variable('c'); $varNode3 = new Expr\Variable('c');
@ -343,7 +343,7 @@ class NodeTraverserTest extends \PHPUnit\Framework\TestCase {
], $visitor->trace); ], $visitor->trace);
} }
public function testReplaceWithNull() { public function testReplaceWithNull(): void {
$one = new Int_(1); $one = new Int_(1);
$else1 = new Else_(); $else1 = new Else_();
$else2 = new Else_(); $else2 = new Else_();
@ -381,7 +381,7 @@ class NodeTraverserTest extends \PHPUnit\Framework\TestCase {
], $visitor2->trace); ], $visitor2->trace);
} }
public function testRemovingVisitor() { public function testRemovingVisitor(): void {
$visitor1 = new class () extends NodeVisitorAbstract {}; $visitor1 = new class () extends NodeVisitorAbstract {};
$visitor2 = new class () extends NodeVisitorAbstract {}; $visitor2 = new class () extends NodeVisitorAbstract {};
$visitor3 = new class () extends NodeVisitorAbstract {}; $visitor3 = new class () extends NodeVisitorAbstract {};
@ -404,7 +404,7 @@ class NodeTraverserTest extends \PHPUnit\Framework\TestCase {
$this->assertSame($postExpected, $getVisitors()); $this->assertSame($postExpected, $getVisitors());
} }
public function testNoCloneNodes() { public function testNoCloneNodes(): void {
$stmts = [new Node\Stmt\Echo_([new String_('Foo'), new String_('Bar')])]; $stmts = [new Node\Stmt\Echo_([new String_('Foo'), new String_('Bar')])];
$traverser = new NodeTraverser(); $traverser = new NodeTraverser();
@ -415,7 +415,7 @@ class NodeTraverserTest extends \PHPUnit\Framework\TestCase {
/** /**
* @dataProvider provideTestInvalidReturn * @dataProvider provideTestInvalidReturn
*/ */
public function testInvalidReturn($stmts, $visitor, $message) { public function testInvalidReturn($stmts, $visitor, $message): void {
$this->expectException(\LogicException::class); $this->expectException(\LogicException::class);
$this->expectExceptionMessage($message); $this->expectExceptionMessage($message);
@ -424,7 +424,7 @@ class NodeTraverserTest extends \PHPUnit\Framework\TestCase {
$traverser->traverse($stmts); $traverser->traverse($stmts);
} }
public function provideTestInvalidReturn() { public static function provideTestInvalidReturn() {
$num = new Node\Scalar\Int_(42); $num = new Node\Scalar\Int_(42);
$expr = new Node\Stmt\Expression($num); $expr = new Node\Stmt\Expression($num);
$stmts = [$expr]; $stmts = [$expr];

View File

@ -7,7 +7,7 @@ use PhpParser\Node\Expr;
use PhpParser\NodeTraverser; use PhpParser\NodeTraverser;
class FindingVisitorTest extends \PHPUnit\Framework\TestCase { class FindingVisitorTest extends \PHPUnit\Framework\TestCase {
public function testFindVariables() { public function testFindVariables(): void {
$traverser = new NodeTraverser(); $traverser = new NodeTraverser();
$visitor = new FindingVisitor(function (Node $node) { $visitor = new FindingVisitor(function (Node $node) {
return $node instanceof Node\Expr\Variable; return $node instanceof Node\Expr\Variable;
@ -27,7 +27,7 @@ class FindingVisitorTest extends \PHPUnit\Framework\TestCase {
], $visitor->getFoundNodes()); ], $visitor->getFoundNodes());
} }
public function testFindAll() { public function testFindAll(): void {
$traverser = new NodeTraverser(); $traverser = new NodeTraverser();
$visitor = new FindingVisitor(function (Node $node) { $visitor = new FindingVisitor(function (Node $node) {
return true; // All nodes return true; // All nodes

View File

@ -7,7 +7,7 @@ use PhpParser\Node\Expr;
use PhpParser\NodeTraverser; use PhpParser\NodeTraverser;
class FirstFindingVisitorTest extends \PHPUnit\Framework\TestCase { class FirstFindingVisitorTest extends \PHPUnit\Framework\TestCase {
public function testFindFirstVariable() { public function testFindFirstVariable(): void {
$traverser = new NodeTraverser(); $traverser = new NodeTraverser();
$visitor = new FirstFindingVisitor(function (Node $node) { $visitor = new FirstFindingVisitor(function (Node $node) {
return $node instanceof Node\Expr\Variable; return $node instanceof Node\Expr\Variable;
@ -21,7 +21,7 @@ class FirstFindingVisitorTest extends \PHPUnit\Framework\TestCase {
$this->assertSame($assign->var, $visitor->getFoundNode()); $this->assertSame($assign->var, $visitor->getFoundNode());
} }
public function testFindNone() { public function testFindNone(): void {
$traverser = new NodeTraverser(); $traverser = new NodeTraverser();
$visitor = new FirstFindingVisitor(function (Node $node) { $visitor = new FirstFindingVisitor(function (Node $node) {
return $node instanceof Node\Expr\BinaryOp; return $node instanceof Node\Expr\BinaryOp;

View File

@ -16,7 +16,7 @@ class NameResolverTest extends \PHPUnit\Framework\TestCase {
/** /**
* @covers \PhpParser\NodeVisitor\NameResolver * @covers \PhpParser\NodeVisitor\NameResolver
*/ */
public function testResolveNames() { public function testResolveNames(): void {
$code = <<<'EOC' $code = <<<'EOC'
<?php <?php
@ -176,7 +176,7 @@ EOC;
/** /**
* @covers \PhpParser\NodeVisitor\NameResolver * @covers \PhpParser\NodeVisitor\NameResolver
*/ */
public function testResolveLocations() { public function testResolveLocations(): void {
$code = <<<'EOC' $code = <<<'EOC'
<?php <?php
namespace NS; namespace NS;
@ -201,6 +201,18 @@ class A extends B implements C, D {
public const X A = X::Bar; public const X A = X::Bar;
public const X\Foo B = X\Foo::Bar; public const X\Foo B = X\Foo::Bar;
public const \X\Foo C = \X\Foo::Bar; public const \X\Foo C = \X\Foo::Bar;
public Foo $foo {
#[X]
set(#[X] Bar $v) {}
}
public function __construct(
public Foo $bar {
#[X]
set(#[X] Bar $v) {}
}
) {}
} }
#[X] #[X]
@ -269,6 +281,18 @@ class A extends \NS\B implements \NS\C, \NS\D
public const \NS\X A = \NS\X::Bar; public const \NS\X A = \NS\X::Bar;
public const \NS\X\Foo B = \NS\X\Foo::Bar; public const \NS\X\Foo B = \NS\X\Foo::Bar;
public const \X\Foo C = \X\Foo::Bar; public const \X\Foo C = \X\Foo::Bar;
public \NS\Foo $foo {
#[\NS\X]
set(#[\NS\X] \NS\Bar $v) {
}
}
public function __construct(public \NS\Foo $bar {
#[\NS\X]
set(#[\NS\X] \NS\Bar $v) {
}
})
{
}
} }
#[\NS\X] #[\NS\X]
interface A extends \NS\C, \NS\D interface A extends \NS\C, \NS\D
@ -328,7 +352,7 @@ EOC;
); );
} }
public function testNoResolveSpecialName() { public function testNoResolveSpecialName(): void {
$stmts = [new Node\Expr\New_(new Name('self'))]; $stmts = [new Node\Expr\New_(new Name('self'))];
$traverser = new PhpParser\NodeTraverser(); $traverser = new PhpParser\NodeTraverser();
@ -337,7 +361,7 @@ EOC;
$this->assertEquals($stmts, $traverser->traverse($stmts)); $this->assertEquals($stmts, $traverser->traverse($stmts));
} }
public function testAddDeclarationNamespacedName() { public function testAddDeclarationNamespacedName(): void {
$nsStmts = [ $nsStmts = [
new Stmt\Class_('A'), new Stmt\Class_('A'),
new Stmt\Interface_('B'), new Stmt\Interface_('B'),
@ -372,7 +396,7 @@ EOC;
$this->assertSame('F', (string) $stmts[0]->stmts[6]->namespacedName); $this->assertSame('F', (string) $stmts[0]->stmts[6]->namespacedName);
} }
public function testAddRuntimeResolvedNamespacedName() { public function testAddRuntimeResolvedNamespacedName(): void {
$stmts = [ $stmts = [
new Stmt\Namespace_(new Name('NS'), [ new Stmt\Namespace_(new Name('NS'), [
new Expr\FuncCall(new Name('foo')), new Expr\FuncCall(new Name('foo')),
@ -398,7 +422,7 @@ EOC;
/** /**
* @dataProvider provideTestError * @dataProvider provideTestError
*/ */
public function testError(Node $stmt, $errorMsg) { public function testError(Node $stmt, $errorMsg): void {
$this->expectException(\PhpParser\Error::class); $this->expectException(\PhpParser\Error::class);
$this->expectExceptionMessage($errorMsg); $this->expectExceptionMessage($errorMsg);
@ -407,7 +431,7 @@ EOC;
$traverser->traverse([$stmt]); $traverser->traverse([$stmt]);
} }
public function provideTestError() { public static function provideTestError() {
return [ return [
[ [
new Stmt\Use_([ new Stmt\Use_([
@ -449,7 +473,7 @@ EOC;
]; ];
} }
public function testClassNameIsCaseInsensitive() { public function testClassNameIsCaseInsensitive(): void {
$source = <<<'EOC' $source = <<<'EOC'
<?php <?php
namespace Foo; namespace Foo;
@ -470,7 +494,7 @@ EOC;
$this->assertSame('Bar\\Baz', $assign->expr->class->name); $this->assertSame('Bar\\Baz', $assign->expr->class->name);
} }
public function testSpecialClassNamesAreCaseInsensitive() { public function testSpecialClassNamesAreCaseInsensitive(): void {
$source = <<<'EOC' $source = <<<'EOC'
<?php <?php
namespace Foo; namespace Foo;
@ -501,7 +525,7 @@ EOC;
$this->assertSame('STATIC', (string) $methodStmt->stmts[2]->expr->class); $this->assertSame('STATIC', (string) $methodStmt->stmts[2]->expr->class);
} }
public function testAddOriginalNames() { public function testAddOriginalNames(): void {
$traverser = new PhpParser\NodeTraverser(); $traverser = new PhpParser\NodeTraverser();
$traverser->addVisitor(new NameResolver(null, ['preserveOriginalNames' => true])); $traverser->addVisitor(new NameResolver(null, ['preserveOriginalNames' => true]));
@ -520,7 +544,7 @@ EOC;
$this->assertSame($n2, $stmts[0]->stmts[1]->name->getAttribute('originalName')); $this->assertSame($n2, $stmts[0]->stmts[1]->name->getAttribute('originalName'));
} }
public function testAttributeOnlyMode() { public function testAttributeOnlyMode(): void {
$traverser = new PhpParser\NodeTraverser(); $traverser = new PhpParser\NodeTraverser();
$traverser->addVisitor(new NameResolver(null, ['replaceNodes' => false])); $traverser->addVisitor(new NameResolver(null, ['replaceNodes' => false]));
@ -543,7 +567,7 @@ EOC;
} }
private function parseAndResolve(string $code): array { private function parseAndResolve(string $code): array {
$parser = new PhpParser\Parser\Php7(new PhpParser\Lexer\Emulative()); $parser = new PhpParser\Parser\Php8(new PhpParser\Lexer\Emulative());
$traverser = new PhpParser\NodeTraverser(); $traverser = new PhpParser\NodeTraverser();
$traverser->addVisitor(new NameResolver()); $traverser->addVisitor(new NameResolver());

View File

@ -10,7 +10,7 @@ use PhpParser\NodeTraverser;
use PhpParser\ParserFactory; use PhpParser\ParserFactory;
final class NodeConnectingVisitorTest extends \PHPUnit\Framework\TestCase { final class NodeConnectingVisitorTest extends \PHPUnit\Framework\TestCase {
public function testConnectsNodeToItsParentNodeAndItsSiblingNodes() { public function testConnectsNodeToItsParentNodeAndItsSiblingNodes(): void {
$ast = (new ParserFactory())->createForNewestSupportedVersion()->parse( $ast = (new ParserFactory())->createForNewestSupportedVersion()->parse(
'<?php if (true) {} else {}' '<?php if (true) {} else {}'
); );

View File

@ -8,7 +8,7 @@ use PhpParser\NodeTraverser;
use PhpParser\ParserFactory; use PhpParser\ParserFactory;
final class ParentConnectingVisitorTest extends \PHPUnit\Framework\TestCase { final class ParentConnectingVisitorTest extends \PHPUnit\Framework\TestCase {
public function testConnectsChildNodeToParentNode() { public function testConnectsChildNodeToParentNode(): void {
$ast = (new ParserFactory())->createForNewestSupportedVersion()->parse( $ast = (new ParserFactory())->createForNewestSupportedVersion()->parse(
'<?php class C { public function m() {} }' '<?php class C { public function m() {} }'
); );

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