mirror of
https://github.com/nikic/PHP-Parser.git
synced 2025-07-15 19:36:51 +02:00
Compare commits
167 Commits
v5.0.0alph
...
v5.4.0
Author | SHA1 | Date | |
---|---|---|---|
447a020a1f | |||
45f70ed80a | |||
74a361814d | |||
6478c5ac53 | |||
8bb415902e | |||
48fd76e542 | |||
d20a197ca7 | |||
62dee28027 | |||
b396e9e0d6 | |||
f212bb7afb | |||
f43324a074 | |||
73b160f8c4 | |||
4f9dc8b0f5 | |||
469377f4a8 | |||
74d3f7fc24 | |||
25828ea952 | |||
05ed79595b | |||
954f7a411f | |||
8eea230464 | |||
9c7a3f8d8f | |||
26573ea64f | |||
3abf7425cd | |||
961f158f6d | |||
e50c67b7a9 | |||
26a0197186 | |||
aedfcc23cd | |||
8d09ba87f4 | |||
23c79fbbfb | |||
b9c8374498 | |||
d0826bd3e4 | |||
5ea6c2938b | |||
b493c51cce | |||
54139ca49b | |||
cde9bab3bb | |||
018da15f3a | |||
ba14437165 | |||
4a22c15169 | |||
514f71024e | |||
6a97061265 | |||
e3f223f623 | |||
03caf4cc99 | |||
b11fc12cce | |||
3beee6ec77 | |||
58de479119 | |||
d3ae2ed679 | |||
beba9c528f | |||
fadccead52 | |||
d8235a2701 | |||
5973c30a46 | |||
b917ba7b9c | |||
caf540443a | |||
683130c2ff | |||
a894652a3d | |||
3ef0811e45 | |||
db1963f9ea | |||
daaadc3bae | |||
7b0384cdbe | |||
d57da64d7c | |||
d327cf2acf | |||
c5ee33df86 | |||
46be4560c4 | |||
f2e037f8ea | |||
b43758e9e9 | |||
09691fc86e | |||
139676794d | |||
70c96493b4 | |||
ec02613432 | |||
af14fdb282 | |||
2218c2252c | |||
ce019e9ad7 | |||
ff095c3c65 | |||
d619c8b4e6 | |||
eb036d5a09 | |||
4a21235f7e | |||
fba1d621c0 | |||
5cc5a67004 | |||
f603e19336 | |||
1eeeb2d525 | |||
255000ad49 | |||
f7d484aa0e | |||
f82a6365a5 | |||
13a41f05a7 | |||
f66650073c | |||
d0b35126e7 | |||
3640d18b87 | |||
b54302f363 | |||
acfccd9d74 | |||
eabae1f7fa | |||
8d50e9d066 | |||
481fec47f4 | |||
4e27a17cd8 | |||
de84f76766 | |||
b4183c2b09 | |||
a1ccf57727 | |||
f5adbb5e3f | |||
5edc190bda | |||
80851163a6 | |||
8f175fe7c8 | |||
d8e8065313 | |||
f4961b89ac | |||
8b9488e1e6 | |||
21fa9c98b3 | |||
0b3c762939 | |||
edee19a5d2 | |||
ea5ba26749 | |||
ab51e9d35a | |||
1873020bf7 | |||
1b346f7935 | |||
2d3dd4e23e | |||
3c52ea9b6d | |||
5a7753a930 | |||
c91c8633a4 | |||
e395f042d2 | |||
21ead39056 | |||
b11fca0310 | |||
06c7ab51b7 | |||
263fa80b81 | |||
5da5231fde | |||
8d58380108 | |||
efe93a171b | |||
ea77807592 | |||
9a68468fda | |||
502b090900 | |||
3c0432b09d | |||
ee3e7db3fc | |||
ba851243f4 | |||
d1d784a5c6 | |||
62853b179c | |||
4b497045e0 | |||
b20267c5ad | |||
748aab3365 | |||
c48ee36f54 | |||
8b1371990c | |||
eaa1d91b4e | |||
3fb4b92f59 | |||
571ca90b7e | |||
16c766eae1 | |||
53f6717329 | |||
5b65f9fc92 | |||
69993a181a | |||
23647573e8 | |||
df3a7057ab | |||
d43edfbb31 | |||
ad8daa12b2 | |||
5883189d61 | |||
afe1628a72 | |||
289756d056 | |||
8490c0e82d | |||
8bc698248d | |||
fb2c3ac97c | |||
c23976a299 | |||
a9dad5c54e | |||
74caed6446 | |||
a5d4c1005c | |||
93731c5cfa | |||
0a8a333a4a | |||
91da19147b | |||
5c267f55c9 | |||
9a5d5c112c | |||
779b6950c3 | |||
b68fb76f14 | |||
36a6dcd04e | |||
bb4263ea1a | |||
4ce0de2d12 | |||
a3bc900a41 | |||
7785d2b887 | |||
aa721520f9 |
5
.gitattributes
vendored
5
.gitattributes
vendored
@ -1,12 +1,17 @@
|
||||
/.github export-ignore
|
||||
/doc export-ignore
|
||||
/grammar export-ignore
|
||||
/test export-ignore
|
||||
/test_old export-ignore
|
||||
/tools export-ignore
|
||||
.editorconfig export-ignore
|
||||
.gitattributes export-ignore
|
||||
.gitignore export-ignore
|
||||
.php-cs-fixer.dist.php export-ignore
|
||||
Makefile export-ignore
|
||||
CHANGELOG.md export-ignore
|
||||
CONTRIBUTING.md export-ignore
|
||||
phpstan-baseline.neon export-ignore
|
||||
phpstan.neon.dist export-ignore
|
||||
phpunit.xml.dist export-ignore
|
||||
UPGRADE-*.md export-ignore
|
||||
|
70
.github/workflows/main.yml
vendored
70
.github/workflows/main.yml
vendored
@ -7,20 +7,20 @@ on:
|
||||
jobs:
|
||||
tests_coverage:
|
||||
runs-on: "ubuntu-latest"
|
||||
name: "PHP 7.1 Unit Tests (with coverage)"
|
||||
name: "PHP 7.4 Unit Tests (with coverage)"
|
||||
steps:
|
||||
- name: "Checkout"
|
||||
uses: "actions/checkout@v2"
|
||||
uses: "actions/checkout@v4"
|
||||
- name: "Install PHP"
|
||||
uses: "shivammathur/setup-php@v2"
|
||||
with:
|
||||
coverage: "xdebug"
|
||||
php-version: "7.1"
|
||||
php-version: "7.4"
|
||||
tools: composer:v2
|
||||
- name: "Install dependencies"
|
||||
run: |
|
||||
composer require php-coveralls/php-coveralls:^2.2 --dev --no-update
|
||||
composer update --no-progress --prefer-dist
|
||||
COMPOSER_ROOT_VERSION=dev-master composer update --no-progress --prefer-dist
|
||||
- name: "Tests"
|
||||
run: "php vendor/bin/phpunit --coverage-clover build/logs/clover.xml"
|
||||
- name: Coveralls
|
||||
@ -34,75 +34,91 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
php-version:
|
||||
- "7.2"
|
||||
- "7.3"
|
||||
- "7.4"
|
||||
- "8.0"
|
||||
- "8.1"
|
||||
- "8.2"
|
||||
- "8.3"
|
||||
- "8.4"
|
||||
fail-fast: false
|
||||
steps:
|
||||
- name: "Checkout"
|
||||
uses: "actions/checkout@v2"
|
||||
uses: "actions/checkout@v4"
|
||||
- name: "Install PHP"
|
||||
uses: "shivammathur/setup-php@v2"
|
||||
with:
|
||||
coverage: "none"
|
||||
php-version: "${{ matrix.php-version }}"
|
||||
ini-file: "development"
|
||||
tools: composer:v2
|
||||
- name: "Install dependencies"
|
||||
run: "composer update --no-progress --prefer-dist ${{ matrix.flags }}"
|
||||
run: "COMPOSER_ROOT_VERSION=dev-master composer update --no-progress --prefer-dist ${{ matrix.flags }}"
|
||||
- name: "PHPUnit"
|
||||
run: "php vendor/bin/phpunit"
|
||||
test_old_73_80:
|
||||
runs-on: "ubuntu-latest"
|
||||
name: "PHP 7.3 Code on PHP 8.0 Integration Tests"
|
||||
name: "PHP 7.4 Code on PHP 8.4 Integration Tests"
|
||||
steps:
|
||||
- name: "Checkout"
|
||||
uses: "actions/checkout@v2"
|
||||
uses: "actions/checkout@v4"
|
||||
- name: "Install PHP"
|
||||
uses: "shivammathur/setup-php@v2"
|
||||
with:
|
||||
coverage: "none"
|
||||
php-version: "8.0"
|
||||
php-version: "8.4"
|
||||
ini-file: "development"
|
||||
tools: composer:v2
|
||||
- name: "Install PHP 8 dependencies"
|
||||
run: "composer update --no-progress --prefer-dist"
|
||||
run: "COMPOSER_ROOT_VERSION=dev-master composer update --no-progress --prefer-dist"
|
||||
- name: "Tests"
|
||||
run: "test_old/run-php-src.sh 7.3.21"
|
||||
run: "test_old/run-php-src.sh 7.4.33"
|
||||
test_old_80_70:
|
||||
runs-on: "ubuntu-latest"
|
||||
name: "PHP 8.2 Code on PHP 7.1 Integration Tests"
|
||||
name: "PHP 8.4 Code on PHP 7.4 Integration Tests"
|
||||
steps:
|
||||
- name: "Checkout"
|
||||
uses: "actions/checkout@v2"
|
||||
uses: "actions/checkout@v4"
|
||||
- name: "Install PHP"
|
||||
uses: "shivammathur/setup-php@v2"
|
||||
with:
|
||||
coverage: "none"
|
||||
php-version: "7.1"
|
||||
php-version: "7.4"
|
||||
ini-file: "development"
|
||||
tools: composer:v2
|
||||
- name: "Install PHP 8 dependencies"
|
||||
run: "composer update --no-progress --prefer-dist"
|
||||
run: "COMPOSER_ROOT_VERSION=dev-master composer update --no-progress --prefer-dist"
|
||||
- name: "Tests"
|
||||
run: "test_old/run-php-src.sh 8.2.3"
|
||||
run: "test_old/run-php-src.sh 8.4.0beta5"
|
||||
phpstan:
|
||||
runs-on: "ubuntu-latest"
|
||||
name: "PHP ${{ matrix.php-version }} PHPStan"
|
||||
strategy:
|
||||
matrix:
|
||||
php-version:
|
||||
- "8.2"
|
||||
name: "PHPStan"
|
||||
steps:
|
||||
- name: "Checkout"
|
||||
uses: "actions/checkout@v2"
|
||||
uses: "actions/checkout@v4"
|
||||
- name: "Install PHP"
|
||||
uses: "shivammathur/setup-php@v2"
|
||||
with:
|
||||
coverage: "none"
|
||||
php-version: "${{ matrix.php-version }}"
|
||||
php-version: "8.3"
|
||||
tools: composer:v2
|
||||
- name: "Install dependencies"
|
||||
run: |
|
||||
run: |
|
||||
cd tools && composer install
|
||||
- name: "PHPStan"
|
||||
run: "php tools/vendor/bin/phpstan"
|
||||
php-cs-fixer:
|
||||
runs-on: "ubuntu-latest"
|
||||
name: "PHP-CS-Fixer"
|
||||
steps:
|
||||
- name: "Checkout"
|
||||
uses: "actions/checkout@v4"
|
||||
- name: "Install PHP"
|
||||
uses: "shivammathur/setup-php@v2"
|
||||
with:
|
||||
coverage: "none"
|
||||
php-version: "8.3"
|
||||
tools: composer:v2
|
||||
- name: "Install dependencies"
|
||||
run: |
|
||||
cd tools && composer install
|
||||
- name: "php-cs-fixer"
|
||||
run: "php tools/vendor/bin/php-cs-fixer fix --dry-run"
|
||||
|
@ -21,6 +21,11 @@ return $config->setRiskyAllowed(true)
|
||||
'declare_strict_types' => true,
|
||||
// Keep argument formatting for now.
|
||||
'method_argument_space' => ['on_multiline' => 'ignore'],
|
||||
'phpdoc_align' => ['align' => 'left'],
|
||||
'phpdoc_trim' => true,
|
||||
'no_empty_phpdoc' => true,
|
||||
'no_superfluous_phpdoc_tags' => ['allow_mixed' => true],
|
||||
'no_extra_blank_lines' => true,
|
||||
])
|
||||
->setFinder($finder)
|
||||
;
|
||||
|
212
CHANGELOG.md
212
CHANGELOG.md
@ -1,3 +1,215 @@
|
||||
Version 5.4.0 (2024-12-30)
|
||||
--------------------------
|
||||
|
||||
### Added
|
||||
|
||||
* Added `Property::isAbstract()` and `Property::isFinal()` methods.
|
||||
* Added `PropertyHook::isFinal()` method.
|
||||
* Emit an error if property hook is used on declaration with multiple properties.
|
||||
|
||||
### Fixed
|
||||
|
||||
* Make legacy class aliases compatible with classmap-authoritative autoloader.
|
||||
* `Param::isPromoted()` and `Param::isPublic()` now returns true for parameters that have property
|
||||
hooks but no explicit visibility modifier.
|
||||
* `PropertyHook::getStmts()` now correctly desugars short `set` hooks. `set => $value` will be
|
||||
expanded to `set { $this->propertyName = $value; }`. This requires the `propertyName` attribute
|
||||
on the hook to be set, which is now also set by the parser. If the attribute is not set,
|
||||
`getStmts()` will throw an error for short set hooks, as it is not possible to produce a correct
|
||||
desugaring.
|
||||
|
||||
Version 5.3.1 (2024-10-08)
|
||||
--------------------------
|
||||
|
||||
### Added
|
||||
|
||||
* Added support for declaring functions with name `exit` or `die`, to allow their use in stubs.
|
||||
|
||||
Version 5.3.0 (2024-09-29)
|
||||
--------------------------
|
||||
|
||||
### Added
|
||||
|
||||
* Added `indent` option to pretty printer, which can be used to specify the indentation to use
|
||||
(defaulting to four spaces). This also allows using tab indentation.
|
||||
|
||||
### Fixed
|
||||
|
||||
* Resolve names in `PropertyHook`s in the `NameResolver`.
|
||||
* Include the trailing semicolon inside `Stmt\GroupUse` nodes, making them consistent with
|
||||
`Stmt\Use_` nodes.
|
||||
* Fixed indentation sometimes becoming negative in formatting-preserving pretty printer, resulting
|
||||
in `ValueError`s.
|
||||
|
||||
Version 5.2.0 (2024-09-15)
|
||||
--------------------------
|
||||
|
||||
### Added
|
||||
|
||||
* [8.4] Added support for `__PROPERTY__` magic constant, represented using a
|
||||
`Node\Scalar\MagicConst\Property` node.
|
||||
* [8.4] Added support for property hooks, which are represented using a new `hooks` subnode on
|
||||
`Node\Stmt\Property` and `Node\Param`, which contains an array of `Node\PropertyHook`.
|
||||
* [8.4] Added support for asymmetric visibility modifiers. Property `flags` can now hold the
|
||||
additional bits `Modifiers::PUBLIC_SET`, `Modifiers::PROTECTED_SET` and `Modifiers::PRIVATE_SET`.
|
||||
* [8.4] Added support for generalized exit function. For backwards compatibility, exit without
|
||||
argument or a single plain argument continues to use a `Node\Expr\Exit_` node. Otherwise (e.g.
|
||||
if a named argument is used) it will be represented as a plain `Node\Expr\FuncCall`.
|
||||
* Added support for passing enum values to various builder methods, like `BuilderFactory::val()`.
|
||||
|
||||
### Removed
|
||||
|
||||
* Removed support for alternative array syntax `$array{0}` from the PHP 8 parser. It is still
|
||||
supported by the PHP 7 parser. This is necessary in order to support property hooks.
|
||||
|
||||
Version 5.1.0 (2024-07-01)
|
||||
--------------------------
|
||||
|
||||
### Added
|
||||
|
||||
* [8.4] Added support for dereferencing `new` expressions without parentheses.
|
||||
|
||||
### Fixed
|
||||
|
||||
* Fixed redundant parentheses being added when pretty printing ternary expressions.
|
||||
|
||||
### Changed
|
||||
|
||||
* Made some phpdoc types more precise.
|
||||
|
||||
Version 5.0.2 (2024-03-05)
|
||||
--------------------------
|
||||
|
||||
### Fixed
|
||||
|
||||
* Fix handling of indentation on next line after opening PHP tag in formatting-preserving pretty
|
||||
printer.
|
||||
|
||||
### Changed
|
||||
|
||||
* Avoid cyclic references in `Parser` objects. This means that no longer used parser objects are
|
||||
immediately destroyed now, instead of requiring cycle GC.
|
||||
* Update `PhpVersion::getNewestSupported()` to report PHP 8.3 instead of PHP 8.2.
|
||||
|
||||
Version 5.0.1 (2024-02-21)
|
||||
--------------------------
|
||||
|
||||
### Changed
|
||||
|
||||
* Added check to detect use of PHP-Parser with libraries that define `T_*` compatibility tokens
|
||||
with incorrect type (such as string instead of int). This would lead to `TypeError`s down the
|
||||
line. Now an `Error` will be thrown early to indicate the problem.
|
||||
|
||||
Version 5.0.0 (2024-01-07)
|
||||
--------------------------
|
||||
|
||||
See UPGRADE-5.0 for detailed migration instructions.
|
||||
|
||||
### Fixed
|
||||
|
||||
* Fixed parent class of `PropertyItem` and `UseItem`.
|
||||
|
||||
Version 5.0.0-rc1 (2023-12-20)
|
||||
------------------------------
|
||||
|
||||
See UPGRADE-5.0 for detailed migration instructions.
|
||||
|
||||
### Fixed
|
||||
|
||||
* Fixed parsing of empty files.
|
||||
|
||||
### Added
|
||||
|
||||
* Added support for printing additional attributes (like `kind`) in `NodeDumper`.
|
||||
* Added `rawValue` attribute to `InterpolatedStringPart` and heredoc/nowdoc `String_`s, which
|
||||
provides the original, unparsed value. It was previously only available for non-interpolated
|
||||
single/double quoted strings.
|
||||
* Added `Stmt\Block` to represent `{}` code blocks. Previously, such code blocks were flattened
|
||||
into the parent statements array. `Stmt\Block` will not be created for structures that are
|
||||
typically used with code blocks, for example `if ($x) { $y; }` will be represented as previously,
|
||||
while `if ($x) { { $x; } }` will have an extra `Stmt\Block` wrapper.
|
||||
|
||||
### Changed
|
||||
|
||||
* Use visitor to assign comments. This fixes the long-standing issue where comments were assigned
|
||||
to all nodes sharing a starting position. Now only the outer-most node will hold the comments.
|
||||
* Don't parse unicode escape sequences when targeting PHP < 7.0.
|
||||
* Improve NodeDumper performance for large dumps.
|
||||
|
||||
### Removed
|
||||
|
||||
* Removed `Stmt\Throw_` node, use `Expr\Throw_` inside `Stmt\Expression` instead.
|
||||
* Removed `ParserFactory::create()`.
|
||||
|
||||
Version 5.0.0-beta1 (2023-09-17)
|
||||
--------------------------------
|
||||
|
||||
See UPGRADE-5.0 for detailed migration instructions.
|
||||
|
||||
### Added
|
||||
|
||||
* Visitors can now be passed directly to the `NodeTraverser` constructor. A separate call to
|
||||
`addVisitor()` is no longer required.
|
||||
|
||||
### Changed
|
||||
|
||||
* The minimum host PHP version is now PHP 7.4. It is still possible to parse code from older
|
||||
versions. Property types have been added where possible.
|
||||
* The `Lexer` no longer accepts options. `Lexer\Emulative` only accepts a `PhpVersion`. The
|
||||
`startLexing()`, `getTokens()` and `handleHaltCompiler()` methods have been removed. Instead,
|
||||
there is a single method `tokenize()` returning the tokens.
|
||||
* The `Parser::getLexer()` method has been replaced by `Parser::getTokens()`.
|
||||
* Attribute handling has been moved from the lexer to the parser, and is no longer configurable.
|
||||
The comments, startLine, endLine, startTokenPos, endTokenPos, startFilePos, and endFilePos
|
||||
attributes will always be added.
|
||||
* The pretty printer now defaults to PHP 7.4 as the target version.
|
||||
* The pretty printer now indents heredoc/nowdoc strings if the target version is >= 7.3
|
||||
(flexible heredoc/nowdoc).
|
||||
|
||||
### Removed
|
||||
|
||||
* The deprecated `Comment::getLine()`, `Comment::getTokenPos()` and `Comment::getFilePos()` methods
|
||||
have been removed. Use `Comment::getStartLine()`, `Comment::getStartTokenPos()` and
|
||||
`Comment::getStartFilePos()` instead.
|
||||
|
||||
### Deprecated
|
||||
|
||||
* The `Node::getLine()` method has been deprecated. Use `Node::getStartLine()` instead.
|
||||
|
||||
Version 5.0.0-alpha3 (2023-06-24)
|
||||
---------------------------------
|
||||
|
||||
See UPGRADE-5.0 for detailed migration instructions.
|
||||
|
||||
### Added
|
||||
|
||||
* [PHP 8.3] Added support for typed constants.
|
||||
* [PHP 8.3] Added support for readonly anonymous classes.
|
||||
* Added support for `NodeVisitor::REPLACE_WITH_NULL`.
|
||||
* Added support for CRLF newlines in the pretty printer, using the new `newline` option.
|
||||
|
||||
### Changed
|
||||
|
||||
* Use PHP 7.1 as the default target version for the pretty printer.
|
||||
* Print `else if { }` instead of `else { if { } }`.
|
||||
* The `leaveNode()` method on visitors is now invoked in reverse order of `enterNode()`.
|
||||
* Moved `NodeTraverser::REMOVE_NODE` etc. to `NodeVisitor::REMOVE_NODE`. The old constants are still
|
||||
available for compatibility.
|
||||
* The `Name` subnode `parts` has been replaced by `name`, which stores the name as a string rather
|
||||
than an array of parts separated by namespace separators. The `getParts()` method returns the old
|
||||
representation.
|
||||
* No longer accept strings for types in Node constructors. Instead, either an `Identifier`, `Name`
|
||||
or `ComplexType` must be passed.
|
||||
* `Comment::getReformattedText()` now normalizes CRLF newlines to LF newlines.
|
||||
|
||||
### Fixed
|
||||
|
||||
* Don't trim leading whitespace in formatting preserving printer.
|
||||
* Treat DEL as a label character in the formatting preserving printer depending on the targeted
|
||||
PHP version.
|
||||
* Fix error reporting in emulative lexer without explicitly specified error handler.
|
||||
* Gracefully handle non-contiguous array indices in the `Differ`.
|
||||
|
||||
Version 5.0.0-alpha2 (2023-03-05)
|
||||
---------------------------------
|
||||
|
||||
|
@ -2,3 +2,31 @@
|
||||
|
||||
This project uses PSR-12 with consistent brace placement. This means that the opening brace is
|
||||
always on the same line, even for class and method declarations.
|
||||
|
||||
## Tools
|
||||
|
||||
This project uses PHP-CS-Fixer and PHPStan. You can invoke them using `make`:
|
||||
|
||||
```shell
|
||||
make php-cs-fixer
|
||||
make phpstan
|
||||
```
|
||||
|
||||
## Adding support for new PHP syntax
|
||||
|
||||
1. If necessary, add emulation support for new tokens.
|
||||
* Add a new subclass of `Lexer\TokenEmulator`. Take inspiration from existing classes.
|
||||
* Add the new class to the array in `Lexer\Emulative`.
|
||||
* Add tests for the emulation in `Lexer\EmulativeTest`. You'll want to modify
|
||||
`provideTestReplaceKeywords()` for new reserved keywords and `provideTestLexNewFeatures()` for
|
||||
other emulations.
|
||||
2. Add any new node classes that are needed.
|
||||
3. Add support for the new syntax in `grammar/php.y`. Regenerate the parser by running
|
||||
`php grammar/rebuildParsers.php`. Use `--debug` if there are conflicts.
|
||||
4. Add pretty-printing support by implementing a `pFooBar()` method in `PrettyPrinter\Standard`.
|
||||
5. Add tests both in `test/code/parser` and `test/code/prettyPrinter`.
|
||||
6. Add support for formatting-preserving pretty-printing. This is done by modifying the data tables
|
||||
at the end of `PrettyPrinterAbstract`. Add a test in `test/code/formatPreservation`.
|
||||
7. Does the new syntax feature namespaced names? If so, add support for name resolution in
|
||||
`NodeVisitor\NameResolver`. Test it in `NodeVisitor\NameResolverTest`.
|
||||
8. Does the new syntax require any changes to builders? Is so, make them :)
|
||||
|
10
Makefile
Normal file
10
Makefile
Normal file
@ -0,0 +1,10 @@
|
||||
.PHONY: phpstan php-cs-fixer
|
||||
|
||||
tools/vendor:
|
||||
composer install -d tools
|
||||
|
||||
phpstan: tools/vendor
|
||||
php tools/vendor/bin/phpstan
|
||||
|
||||
php-cs-fixer: tools/vendor
|
||||
php tools/vendor/bin/php-cs-fixer fix
|
25
README.md
25
README.md
@ -6,11 +6,9 @@ PHP Parser
|
||||
This is a PHP parser written in PHP. Its purpose is to simplify static code analysis and
|
||||
manipulation.
|
||||
|
||||
[Documentation for version 5.x][doc_master] (in development; for running on PHP >= 7.1; for parsing PHP 7.0 to PHP 8.2, with limited support for parsing PHP 5.x).
|
||||
[**Documentation for version 5.x**][doc_master] (current; for running on PHP >= 7.4; for parsing PHP 7.0 to PHP 8.4, with limited support for parsing PHP 5.x).
|
||||
|
||||
[**Documentation for version 4.x**][doc_4_x] (stable; for running on PHP >= 7.0; for parsing PHP 5.2 to PHP 8.2).
|
||||
|
||||
[Documentation for version 3.x][doc_3_x] (unsupported; for running on PHP >= 5.5; for parsing PHP 5.2 to PHP 7.2).
|
||||
[Documentation for version 4.x][doc_4_x] (supported; for running on PHP >= 7.0; for parsing PHP 5.2 to PHP 8.3).
|
||||
|
||||
Features
|
||||
--------
|
||||
@ -70,12 +68,17 @@ This dumps an AST looking something like this:
|
||||
```
|
||||
array(
|
||||
0: Stmt_Function(
|
||||
attrGroups: array(
|
||||
)
|
||||
byRef: false
|
||||
name: Identifier(
|
||||
name: test
|
||||
)
|
||||
params: array(
|
||||
0: Param(
|
||||
attrGroups: array(
|
||||
)
|
||||
flags: 0
|
||||
type: null
|
||||
byRef: false
|
||||
variadic: false
|
||||
@ -90,12 +93,11 @@ array(
|
||||
0: Stmt_Expression(
|
||||
expr: Expr_FuncCall(
|
||||
name: Name(
|
||||
parts: array(
|
||||
0: var_dump
|
||||
)
|
||||
name: var_dump
|
||||
)
|
||||
args: array(
|
||||
0: Arg(
|
||||
name: null
|
||||
value: Expr_Variable(
|
||||
name: foo
|
||||
)
|
||||
@ -137,12 +139,16 @@ This gives us an AST where the `Function_::$stmts` are empty:
|
||||
```
|
||||
array(
|
||||
0: Stmt_Function(
|
||||
attrGroups: array(
|
||||
)
|
||||
byRef: false
|
||||
name: Identifier(
|
||||
name: test
|
||||
)
|
||||
params: array(
|
||||
0: Param(
|
||||
attrGroups: array(
|
||||
)
|
||||
type: null
|
||||
byRef: false
|
||||
variadic: false
|
||||
@ -205,9 +211,8 @@ Component documentation:
|
||||
* [AST builders](doc/component/AST_builders.markdown)
|
||||
* Fluent builders for AST nodes
|
||||
* [Lexer](doc/component/Lexer.markdown)
|
||||
* Lexer options
|
||||
* Token and file positions for nodes
|
||||
* Custom attributes
|
||||
* Emulation
|
||||
* Tokens, positions and attributes
|
||||
* [Error handling](doc/component/Error_handling.markdown)
|
||||
* Column information for errors
|
||||
* Error recovery (parsing of syntactically incorrect code)
|
||||
|
335
UPGRADE-5.0.md
335
UPGRADE-5.0.md
@ -3,7 +3,7 @@ Upgrading from PHP-Parser 4.x to 5.0
|
||||
|
||||
### PHP version requirements
|
||||
|
||||
PHP-Parser now requires PHP 7.1 or newer to run. It is however still possible to *parse* code for older versions, while running on a newer version.
|
||||
PHP-Parser now requires PHP 7.4 or newer to run. It is however still possible to *parse* code for older versions, while running on a newer version.
|
||||
|
||||
### PHP 5 parsing support
|
||||
|
||||
@ -13,6 +13,7 @@ In particular, if an older `PhpVersion` is specified, then:
|
||||
|
||||
* For versions before PHP 7.0, `$foo =& new Bar()` assignments are allowed without error.
|
||||
* For versions before PHP 7.0, invalid octal literals `089` are allowed without error.
|
||||
* For versions before PHP 7.0, unicode escape sequences `\u{123}` in strings are not parsed.
|
||||
* Type hints are interpreted as a class `Name` or as a built-in `Identifier` depending on PHP
|
||||
version, for example `int` is treated as a class name on PHP 5.6 and as a built-in on PHP 7.0.
|
||||
|
||||
@ -27,16 +28,17 @@ The following symbols are affected by this removal:
|
||||
* The `PhpParser\Parser\Php5` class has been removed.
|
||||
* The `PhpParser\Parser\Multiple` class has been removed. While not strictly related to PHP 5 support, this functionality is no longer useful without it.
|
||||
* The `PhpParser\ParserFactory::ONLY_PHP5` and `PREFER_PHP5` options have been removed.
|
||||
* The `PhpParser\ParserFactory::PREFER_PHP7` option is now equivalent to `ONLY_PHP7`.
|
||||
|
||||
### Changes to the parser factory
|
||||
|
||||
The `ParserFactory::create()` method is deprecated in favor of three new methods that provide more fine-grained control over the PHP version being targeted:
|
||||
The `ParserFactory::create()` method has been removed in favor of three new methods that provide more fine-grained control over the PHP version being targeted:
|
||||
|
||||
* `createForNewestSupportedVersion()`: Use this if you don't know the PHP version of the code you're parsing. It's better to assume a too new version than a too old one.
|
||||
* `createForHostVersion()`: Use this if you're parsing code for the PHP version you're running on.
|
||||
* `createForVersion()`: Use this if you know the PHP version of the code you want to parse.
|
||||
|
||||
The `createForNewestSupportedVersion()` and `creatForHostVersion()` are available since PHP-Parser 4.18.0, to allow libraries to support PHP-Parser 4 and 5 at the same time more easily.
|
||||
|
||||
In all cases, the PHP version is a fairly weak hint that is only used on a best-effort basis. The parser will usually accept code for newer versions if it does not have any backwards-compatibility implications.
|
||||
|
||||
For example, if you specify version `"8.0"`, then `class ReadOnly {}` is treated as a valid class declaration, while using `public readonly int $prop` will lead to a parse error. However, `final public const X = Y;` will be accepted in both cases.
|
||||
@ -45,7 +47,7 @@ For example, if you specify version `"8.0"`, then `class ReadOnly {}` is treated
|
||||
use PhpParser\ParserFactory;
|
||||
use PhpParser\PhpVersion;
|
||||
|
||||
$factory = new ParserFactory;
|
||||
$factory = new ParserFactory();
|
||||
|
||||
# Before
|
||||
$parser = $factory->create(ParserFactory::PREFER_PHP7);
|
||||
@ -61,6 +63,35 @@ $parser = $factory->create(ParserFactory::ONLY_PHP5);
|
||||
$parser = $factory->createForVersion(PhpVersion::fromString("5.6"));
|
||||
```
|
||||
|
||||
### Changes to the throw representation
|
||||
|
||||
Previously, `throw` statements like `throw $e;` were represented using the `Stmt\Throw_` class,
|
||||
while uses inside other expressions (such as `$x ?? throw $e`) used the `Expr\Throw_` class.
|
||||
|
||||
Now, `throw $e;` is represented as a `Stmt\Expression` that contains an `Expr\Throw_`. The
|
||||
`Stmt\Throw_` class has been removed.
|
||||
|
||||
```php
|
||||
# Code
|
||||
throw $e;
|
||||
|
||||
# Before
|
||||
Stmt_Throw(
|
||||
expr: Expr_Variable(
|
||||
name: e
|
||||
)
|
||||
)
|
||||
|
||||
# After
|
||||
Stmt_Expression(
|
||||
expr: Expr_Throw(
|
||||
expr: Expr_Variable(
|
||||
name: e
|
||||
)
|
||||
)
|
||||
)
|
||||
```
|
||||
|
||||
### Changes to the array destructuring representation
|
||||
|
||||
Previously, the `list($x) = $y` destructuring syntax was represented using a `Node\Expr\List_`
|
||||
@ -71,6 +102,177 @@ Now, destructuring is always represented using `Node\Expr\List_`. The `kind` att
|
||||
`Node\Expr\List_::KIND_LIST` or `Node\Expr\List_::KIND_ARRAY` specifies which syntax was actually
|
||||
used.
|
||||
|
||||
```php
|
||||
# Code
|
||||
[$x] = $y;
|
||||
|
||||
# Before
|
||||
Expr_Assign(
|
||||
var: Expr_Array(
|
||||
items: array(
|
||||
0: Expr_ArrayItem(
|
||||
key: null
|
||||
value: Expr_Variable(
|
||||
name: x
|
||||
)
|
||||
byRef: false
|
||||
unpack: false
|
||||
)
|
||||
)
|
||||
)
|
||||
expr: Expr_Variable(
|
||||
name: y
|
||||
)
|
||||
)
|
||||
|
||||
# After
|
||||
Expr_Assign(
|
||||
var: Expr_List(
|
||||
items: array(
|
||||
0: ArrayItem(
|
||||
key: null
|
||||
value: Expr_Variable(
|
||||
name: x
|
||||
)
|
||||
byRef: false
|
||||
unpack: false
|
||||
)
|
||||
)
|
||||
)
|
||||
expr: Expr_Variable(
|
||||
name: y
|
||||
)
|
||||
)
|
||||
```
|
||||
|
||||
### Changes to the name representation
|
||||
|
||||
Previously, `Name` nodes had a `parts` subnode, which stores an array of name parts, split by
|
||||
namespace separators. Now, `Name` nodes instead have a `name` subnode, which stores a plain string.
|
||||
|
||||
For example, the name `Foo\Bar` was previously represented by `Name(parts: ['Foo', 'Bar'])` and is
|
||||
now represented by `Name(name: 'Foo\Bar')` instead.
|
||||
|
||||
It is possible to convert the name to the previous representation using `$name->getParts()`. The
|
||||
`Name` constructor continues to accept both the string and the array representation.
|
||||
|
||||
The `Name::getParts()` method is available since PHP-Parser 4.16.0, to allow libraries to support
|
||||
PHP-Parser 4 and 5 at the same time more easily.
|
||||
|
||||
### Changes to the block representation
|
||||
|
||||
Previously, code blocks `{ ... }` were always flattened into their parent statement list. For
|
||||
example `while ($x) { $a; { $b; } $c; }` would produce the same node structure as
|
||||
`if ($x) { $a; $b; $c; }`, namely a `Stmt\While_` node whose `stmts` subnode is an array of three
|
||||
statements.
|
||||
|
||||
Now, the nested `{ $b; }` block is represented using an explicit `Stmt\Block` node. However, the
|
||||
outer `{ $a; { $b; } $c; }` block is still represented using a simple array in the `stmts` subnode.
|
||||
|
||||
```php
|
||||
# Code
|
||||
while ($x) { $a; { $b; } $c; }
|
||||
|
||||
# Before
|
||||
Stmt_While(
|
||||
cond: Expr_Variable(
|
||||
name: x
|
||||
)
|
||||
stmts: array(
|
||||
0: Stmt_Expression(
|
||||
expr: Expr_Variable(
|
||||
name: a
|
||||
)
|
||||
)
|
||||
1: Stmt_Expression(
|
||||
expr: Expr_Variable(
|
||||
name: b
|
||||
)
|
||||
)
|
||||
2: Stmt_Expression(
|
||||
expr: Expr_Variable(
|
||||
name: c
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
# After
|
||||
Stmt_While(
|
||||
cond: Expr_Variable(
|
||||
name: x
|
||||
)
|
||||
stmts: array(
|
||||
0: Stmt_Expression(
|
||||
expr: Expr_Variable(
|
||||
name: a
|
||||
)
|
||||
)
|
||||
1: Stmt_Block(
|
||||
stmts: array(
|
||||
0: Stmt_Expression(
|
||||
expr: Expr_Variable(
|
||||
name: b
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
2: Stmt_Expression(
|
||||
expr: Expr_Variable(
|
||||
name: c
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
```
|
||||
|
||||
### Changes to comment assignment
|
||||
|
||||
Previously, comments were assigned to all nodes starting at the same position. Now they will be
|
||||
assigned to the outermost node only.
|
||||
|
||||
```php
|
||||
# Code
|
||||
// Comment
|
||||
$a + $b;
|
||||
|
||||
# Before
|
||||
Stmt_Expression(
|
||||
expr: Expr_BinaryOp_Plus(
|
||||
left: Expr_Variable(
|
||||
name: a
|
||||
comments: array(
|
||||
0: // Comment
|
||||
)
|
||||
)
|
||||
right: Expr_Variable(
|
||||
name: b
|
||||
)
|
||||
comments: array(
|
||||
0: // Comment
|
||||
)
|
||||
)
|
||||
comments: array(
|
||||
0: // Comment
|
||||
)
|
||||
)
|
||||
|
||||
# After
|
||||
Stmt_Expression(
|
||||
expr: Expr_BinaryOp_Plus(
|
||||
left: Expr_Variable(
|
||||
name: a
|
||||
)
|
||||
right: Expr_Variable(
|
||||
name: b
|
||||
)
|
||||
)
|
||||
comments: array(
|
||||
0: // Comment
|
||||
)
|
||||
)
|
||||
```
|
||||
|
||||
### Renamed nodes
|
||||
|
||||
A number of AST nodes have been renamed or moved in the AST hierarchy:
|
||||
@ -93,7 +295,7 @@ The old class names have been retained as aliases for backwards compatibility. H
|
||||
|
||||
Modifier flags (as used by the `$flags` subnode of `Class_`, `ClassMethod`, `Property`, etc.) are now available as class constants on a separate `PhpParser\Modifiers` class, instead of being part of `PhpParser\Node\Stmt\Class_`, to make it clearer that these are used by many different nodes. The old constants are deprecated, but are still available.
|
||||
|
||||
```
|
||||
```php
|
||||
PhpParser\Node\Stmt\Class_::MODIFIER_PUBLIC -> PhpParser\Modifiers::PUBLIC
|
||||
PhpParser\Node\Stmt\Class_::MODIFIER_PROTECTED -> PhpParser\Modifiers::PROTECTED
|
||||
PhpParser\Node\Stmt\Class_::MODIFIER_PRIVATE -> PhpParser\Modifiers::PRIVATE
|
||||
@ -104,6 +306,21 @@ PhpParser\Node\Stmt\Class_::MODIFIER_READONLY -> PhpParser\Modifiers::READONLY
|
||||
PhpParser\Node\Stmt\Class_::VISIBILITY_MODIFIER_MASK -> PhpParser\Modifiers::VISIBILITY_MASK
|
||||
```
|
||||
|
||||
### Changes to node constructors
|
||||
|
||||
Node constructor arguments accepting types now longer accept plain strings. Either an `Identifier` or `Name` (or `ComplexType`) should be passed instead. This affects the following constructor arguments:
|
||||
|
||||
* The `'returnType'` key of `$subNodes` argument of `Node\Expr\ArrowFunction`.
|
||||
* The `'returnType'` key of `$subNodes` argument of `Node\Expr\Closure`.
|
||||
* The `'returnType'` key of `$subNodes` argument of `Node\Stmt\ClassMethod`.
|
||||
* The `'returnType'` key of `$subNodes` argument of `Node\Stmt\Function_`.
|
||||
* The `$type` argument of `Node\NullableType`.
|
||||
* The `$type` argument of `Node\Param`.
|
||||
* The `$type` argument of `Node\Stmt\Property`.
|
||||
* The `$type` argument of `Node\ClassConst`.
|
||||
|
||||
To follow the previous behavior, an `Identifier` should be passed, which indicates a built-in type.
|
||||
|
||||
### Changes to the pretty printer
|
||||
|
||||
A number of changes to the standard pretty printer have been made, to make it match contemporary coding style conventions (and in particular PSR-12). Options to restore the previous behavior are not provided, but it is possible to override the formatting methods (such as `pStmt_ClassMethod`) with your preferred formatting.
|
||||
@ -156,13 +373,29 @@ Backslashes in single-quoted strings are now only printed if they are necessary:
|
||||
'\\\\';
|
||||
```
|
||||
|
||||
The pretty printer now accepts a `phpVersion` option, which accepts a `PhpVersion` object and defaults to PHP 7.0. The pretty printer will make formatting choices to make the code valid for that version. It currently controls the following behavior:
|
||||
`else if` structures will now omit redundant parentheses:
|
||||
|
||||
```php
|
||||
# Before
|
||||
else {
|
||||
if ($x) {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
|
||||
# After
|
||||
else if ($x) {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
The pretty printer now accepts a `phpVersion` option, which accepts a `PhpVersion` object and defaults to PHP 7.4. The pretty printer will make formatting choices to make the code valid for that version. It currently controls the following behavior:
|
||||
|
||||
* For PHP >= 7.0 (default), short array syntax `[]` will be used by default. This does not affect nodes that specify an explicit array syntax using the `kind` attribute.
|
||||
* For PHP >= 7.0 (default), parentheses around `yield` expressions will only be printed when necessary. Previously, parentheses were always printed, even if `yield` was used as a statement.
|
||||
* For PHP >= 7.1, the short array syntax `[]` will be used for destructuring by default (instead of
|
||||
`list()`). This does not affect nodes that specify and explicit syntax using the `kind` attribute.
|
||||
* For PHP >= 7.3, a newline is no longer forced after heredoc/nowdoc strings, as the requirement for this has been removed with the introduction of flexible heredoc/nowdoc strings.
|
||||
* For PHP >= 7.1 (default), the short array syntax `[]` will be used for destructuring by default (instead of `list()`). This does not affect nodes that specify an explicit syntax using the `kind` attribute.
|
||||
* For PHP >= 7.3 (default), a newline is no longer forced after heredoc/nowdoc strings, as the requirement for this has been removed with the introduction of flexible heredoc/nowdoc strings.
|
||||
* For PHP >= 7.3 (default), heredoc/nowdoc strings are now indented just like regular code. This was allowed with the introduction of flexible heredoc/nowdoc strings.
|
||||
|
||||
### Changes to precedence handling in the pretty printer
|
||||
|
||||
@ -178,7 +411,7 @@ protected function p(
|
||||
|
||||
The `$precedence` is the precedence of the direct parent operator (if any), while `$lhsPrecedence` is that precedence of the nearest binary operator on whose left-hand-side the node occurs. For unary operators, only the `$lhsPrecedence` is relevant.
|
||||
|
||||
Recursive calls in pretty-printer methods should generally continue calling `p()` without additional parameters. However, pretty-printer methods for operators that participate in precedence resolution need to be adjusted. For example, typical implementations for operators looks as follows now:
|
||||
Recursive calls in pretty-printer methods should generally continue calling `p()` without additional parameters. However, pretty-printer methods for operators that participate in precedence resolution need to be adjusted. For example, typical implementations for operators look as follows now:
|
||||
|
||||
```php
|
||||
protected function pExpr_BinaryOp_Plus(
|
||||
@ -197,6 +430,44 @@ protected function pExpr_UnaryPlus(
|
||||
|
||||
The new `$precedence` and `$lhsPrecedence` arguments need to be passed down to the `pInfixOp()`, `pPrefixOp()` and `pPostfixOp()` methods.
|
||||
|
||||
### Changes to the node traverser
|
||||
|
||||
If there are multiple visitors, the node traverser will now call `leaveNode()` and `afterTraverse()` methods in the reverse order of the corresponding `enterNode()` and `beforeTraverse()` calls:
|
||||
|
||||
```php
|
||||
# Before
|
||||
$visitor1->enterNode($node);
|
||||
$visitor2->enterNode($node);
|
||||
$visitor1->leaveNode($node);
|
||||
$visitor2->leaveNode($node);
|
||||
|
||||
# After
|
||||
$visitor1->enterNode($node);
|
||||
$visitor2->enterNode($node);
|
||||
$visitor2->leaveNode($node);
|
||||
$visitor1->leaveNode($node);
|
||||
```
|
||||
|
||||
Additionally, the special `NodeVisitor` return values have been moved from `NodeTraverser` to `NodeVisitor`. The old names are deprecated, but still available.
|
||||
|
||||
```php
|
||||
PhpParser\NodeTraverser::REMOVE_NODE -> PhpParser\NodeVisitor::REMOVE_NODE
|
||||
PhpParser\NodeTraverser::DONT_TRAVERSE_CHILDREN -> PhpParser\NodeVisitor::DONT_TRAVERSE_CHILDREN
|
||||
PhpParser\NodeTraverser::DONT_TRAVERSE_CURRENT_AND_CHILDREN -> PhpParser\NodeVisitor::DONT_TRAVERSE_CURRENT_AND_CHILDREN
|
||||
PhpParser\NodeTraverser::STOP_TRAVERSAL -> PhpParser\NodeVisitor::STOP_TRAVERSAL
|
||||
```
|
||||
|
||||
Visitors can now also be passed directly to the `NodeTraverser` constructor:
|
||||
|
||||
```php
|
||||
# Before (and still supported)
|
||||
$traverser = new NodeTraverser();
|
||||
$traverser->addVisitor(new NameResolver());
|
||||
|
||||
# After
|
||||
$traverser = new NodeTraverser(new NameResolver());
|
||||
```
|
||||
|
||||
### Changes to token representation
|
||||
|
||||
Tokens are now internally represented using the `PhpParser\Token` class, which exposes the same base interface as
|
||||
@ -214,10 +485,50 @@ class Token {
|
||||
}
|
||||
```
|
||||
|
||||
The `Lexer::getTokens()` method will now return an array of `Token`s, rather than an array of arrays and strings.
|
||||
The token array is now an array of `Token`s, rather than an array of arrays and strings.
|
||||
Additionally, the token array is now terminated by a sentinel token with ID 0.
|
||||
|
||||
### Other removed functionality
|
||||
### Changes to the lexer
|
||||
|
||||
The lexer API is reduced to a single `Lexer::tokenize()` method, which returns an array of tokens. The `startLexing()` and `getNextToken()` methods have been removed.
|
||||
|
||||
Responsibility for determining start and end attributes for nodes has been moved from the lexer to the parser. The lexer no longer accepts an options array. The `usedAttributes` option has been removed without replacement, and the parser will now unconditionally add the `comments`, `startLine`, `endLine`, `startFilePos`, `endFilePos`, `startTokenPos` and `endTokenPos` attributes.
|
||||
|
||||
There should no longer be a need to directly interact with the `Lexer` for end users, as the `ParserFactory` will create an appropriate instance, and no additional configuration of the lexer is necessary. To use formatting-preserving pretty printing, the setup boilerplate changes as follows:
|
||||
|
||||
```php
|
||||
# Before
|
||||
|
||||
$lexer = new Lexer\Emulative([
|
||||
'usedAttributes' => [
|
||||
'comments',
|
||||
'startLine', 'endLine',
|
||||
'startTokenPos', 'endTokenPos',
|
||||
],
|
||||
]);
|
||||
|
||||
$parser = new Parser\Php7($lexer);
|
||||
$oldStmts = $parser->parse($code);
|
||||
$oldTokens = $lexer->getTokens();
|
||||
|
||||
$traverser = new NodeTraverser();
|
||||
$traverser->addVisitor(new NodeVisitor\CloningVisitor());
|
||||
$newStmts = $traverser->traverse($oldStmts);
|
||||
|
||||
# After
|
||||
|
||||
$parser = (new ParserFactory())->createForNewestSupportedVersion();
|
||||
$oldStmts = $parser->parse($code);
|
||||
$oldTokens = $parser->getTokens();
|
||||
|
||||
$traverser = new NodeTraverser(new NodeVisitor\CloningVisitor());
|
||||
$newStmts = $traverser->traverse($oldStmts);
|
||||
```
|
||||
|
||||
### Miscellaneous changes
|
||||
|
||||
* The deprecated `Builder\Param::setTypeHint()` method has been removed in favor of `Builder\Param::setType()`.
|
||||
* The deprecated `Error` constructor taking a start line has been removed. Pass `['startLine' => $startLine]` attributes instead.
|
||||
* The deprecated `Comment::getLine()`, `Comment::getTokenPos()` and `Comment::getFilePos()` methods have been removed. Use `Comment::getStartLine()`, `Comment::getStartTokenPos()` and `Comment::getStartFilePos()` instead.
|
||||
* `Comment::getReformattedText()` now normalizes CRLF newlines to LF newlines.
|
||||
* The `Node::getLine()` method has been deprecated. Use `Node::getStartLine()` instead.
|
||||
|
@ -26,10 +26,7 @@ if (empty($files)) {
|
||||
showHelp("Must specify at least one file.");
|
||||
}
|
||||
|
||||
$lexerOptions = ['usedAttributes' => [
|
||||
'startLine', 'endLine', 'startFilePos', 'endFilePos', 'comments'
|
||||
]];
|
||||
$parser = (new PhpParser\ParserFactory())->createForVersion($attributes['version'], $lexerOptions);
|
||||
$parser = (new PhpParser\ParserFactory())->createForVersion($attributes['version']);
|
||||
$dumper = new PhpParser\NodeDumper([
|
||||
'dumpComments' => true,
|
||||
'dumpPositions' => $attributes['with-positions'],
|
||||
|
@ -13,13 +13,13 @@
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": ">=7.1",
|
||||
"php": ">=7.4",
|
||||
"ext-tokenizer": "*",
|
||||
"ext-json": "*",
|
||||
"ext-ctype": "*"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^7.0 || ^8.0 || ^9.0",
|
||||
"phpunit/phpunit": "^9.0",
|
||||
"ircmaxell/php-yacc": "^0.0.7"
|
||||
},
|
||||
"extra": {
|
||||
|
@ -13,14 +13,14 @@ application dealing with code programmatically. A parser constructs an [Abstract
|
||||
There are other ways of processing source code. One that PHP supports natively is using the
|
||||
token stream generated by [`token_get_all`][2]. The token stream is much more low level than
|
||||
the AST and thus has different applications: It allows to also analyze the exact formatting of
|
||||
a file. On the other hand the token stream is much harder to deal with for more complex analysis.
|
||||
a file. On the other hand, the token stream is much harder to deal with for more complex analysis.
|
||||
For example, an AST abstracts away the fact that, in PHP, variables can be written as `$foo`, but also
|
||||
as `$$bar`, `${'foobar'}` or even `${!${''}=barfoo()}`. You don't have to worry about recognizing
|
||||
all the different syntaxes from a stream of tokens.
|
||||
|
||||
Another question is: Why would I want to have a PHP parser *written in PHP*? Well, PHP might not be
|
||||
a language especially suited for fast parsing, but processing the AST is much easier in PHP than it
|
||||
would be in other, faster languages like C. Furthermore the people most probably wanting to do
|
||||
would be in other, faster languages like C. Furthermore the people most likely wanting to do
|
||||
programmatic PHP code analysis are incidentally PHP developers, not C developers.
|
||||
|
||||
What can it parse?
|
||||
@ -43,7 +43,7 @@ following caveats:
|
||||
|
||||
As the parser is based on the tokens returned by `token_get_all` (which is only able to lex the PHP
|
||||
version it runs on), additionally a wrapper for emulating tokens from newer versions is provided.
|
||||
This allows to parse PHP 8.0 source code running on PHP 7.1, for example. This emulation is not
|
||||
This allows to parse PHP 8.4 source code running on PHP 7.4, for example. This emulation is not
|
||||
perfect, but works well in practice.
|
||||
|
||||
Finally, it should be noted that the parser aims to accept all valid code, not reject all invalid
|
||||
@ -81,16 +81,16 @@ However, it does retain accurate position information, which can be used to insp
|
||||
What else can it do?
|
||||
--------------------
|
||||
|
||||
Apart from the parser itself this package also bundles support for some other, related features:
|
||||
Apart from the parser itself, this package also bundles support for some other, related features:
|
||||
|
||||
* Support for pretty printing, which is the act of converting an AST into PHP code. Please note
|
||||
that "pretty printing" does not imply that the output is especially pretty. It's just how it's
|
||||
called ;)
|
||||
* Support for serializing and unserializing the node tree to JSON
|
||||
* Support for serializing and unserializing the node tree to JSON.
|
||||
* Support for dumping the node tree in a human-readable form (see the section above for an
|
||||
example of how the output looks like)
|
||||
* Infrastructure for traversing and changing the AST (node traverser and node visitors)
|
||||
* A node visitor for resolving namespaced names
|
||||
example of how the output looks like).
|
||||
* Infrastructure for traversing and changing the AST (node traverser and node visitors).
|
||||
* A node visitor for resolving namespaced names.
|
||||
|
||||
[0]: http://en.wikipedia.org/wiki/Static_program_analysis
|
||||
[1]: http://en.wikipedia.org/wiki/Abstract_syntax_tree
|
||||
|
@ -50,7 +50,7 @@ The `createXYZ()` methods optionally accept an array of lexer options. Some use
|
||||
customized lexer options are discussed in the [lexer documentation](component/Lexer.markdown).
|
||||
|
||||
Subsequently, you can pass PHP code (including the opening `<?php` tag) to the `parse()` method in
|
||||
order to create a syntax tree. If a syntax error is encountered, an `PhpParser\Error` exception will
|
||||
order to create a syntax tree. If a syntax error is encountered, a `PhpParser\Error` exception will
|
||||
be thrown by default:
|
||||
|
||||
```php
|
||||
@ -72,7 +72,7 @@ try {
|
||||
$stmts = $parser->parse($code);
|
||||
// $stmts is an array of statement nodes
|
||||
} catch (Error $e) {
|
||||
echo 'Parse Error: ', $e->getMessage();
|
||||
echo 'Parse Error: ', $e->getMessage(), "\n";
|
||||
}
|
||||
```
|
||||
|
||||
@ -96,12 +96,17 @@ For the sample code from the previous section, this will produce the following o
|
||||
```
|
||||
array(
|
||||
0: Stmt_Function(
|
||||
attrGroups: array(
|
||||
)
|
||||
byRef: false
|
||||
name: Identifier(
|
||||
name: printLine
|
||||
)
|
||||
params: array(
|
||||
0: Param(
|
||||
attrGroups: array(
|
||||
)
|
||||
flags: 0
|
||||
type: null
|
||||
byRef: false
|
||||
variadic: false
|
||||
@ -129,12 +134,11 @@ array(
|
||||
1: Stmt_Expression(
|
||||
expr: Expr_FuncCall(
|
||||
name: Name(
|
||||
parts: array(
|
||||
0: printLine
|
||||
)
|
||||
name: printLine
|
||||
)
|
||||
args: array(
|
||||
0: Arg(
|
||||
name: null
|
||||
value: Scalar_String(
|
||||
value: Hello World!!!
|
||||
)
|
||||
@ -202,11 +206,13 @@ without the `PhpParser\Node\` prefix and `\` replaced with `_`. It also does not
|
||||
It is possible to associate custom metadata with a node using the `setAttribute()` method. This data
|
||||
can then be retrieved using `hasAttribute()`, `getAttribute()` and `getAttributes()`.
|
||||
|
||||
By default the lexer adds the `startLine`, `endLine` and `comments` attributes. `comments` is an array
|
||||
of `PhpParser\Comment[\Doc]` instances.
|
||||
By default, the parser adds the `startLine`, `endLine`, `startTokenPos`, `endTokenPos`,
|
||||
`startFilePos`, `endFilePos` and `comments` attributes. `comments` is an array of
|
||||
`PhpParser\Comment[\Doc]` instances.
|
||||
|
||||
The start line can also be accessed using `getStartLine()` (instead of `getAttribute('startLine')`).
|
||||
The last doc comment from the `comments` attribute can be obtained using `getDocComment()`.
|
||||
The pre-defined attributes can also be accessed using `getStartLine()` instead of
|
||||
`getAttribute('startLine')`, and so on. The last doc comment from the `comments` attribute can be
|
||||
obtained using `getDocComment()`.
|
||||
|
||||
Pretty printer
|
||||
--------------
|
||||
@ -240,7 +246,7 @@ try {
|
||||
|
||||
echo $code;
|
||||
} catch (Error $e) {
|
||||
echo 'Parse Error: ', $e->getMessage();
|
||||
echo 'Parse Error: ', $e->getMessage(), "\n";
|
||||
}
|
||||
```
|
||||
|
||||
@ -248,7 +254,7 @@ The above code will output:
|
||||
|
||||
echo 'Hello ', hi\getTarget();
|
||||
|
||||
As you can see the source code was first parsed using `PhpParser\Parser->parse()`, then changed and then
|
||||
As you can see, the source code was first parsed using `PhpParser\Parser->parse()`, then changed and then
|
||||
again converted to code using `PhpParser\PrettyPrinter\Standard->prettyPrint()`.
|
||||
|
||||
The `prettyPrint()` method pretty prints a statements array. It is also possible to pretty print only a
|
||||
@ -343,15 +349,18 @@ i.e. before its subnodes are traversed, the latter when it is left.
|
||||
All four methods can either return the changed node or not return at all (i.e. `null`) in which
|
||||
case the current node is not changed.
|
||||
|
||||
The `enterNode()` method can additionally return the value `NodeTraverser::DONT_TRAVERSE_CHILDREN`,
|
||||
The `enterNode()` method can additionally return the value `NodeVisitor::DONT_TRAVERSE_CHILDREN`,
|
||||
which instructs the traverser to skip all children of the current node. To furthermore prevent subsequent
|
||||
visitors from visiting the current node, `NodeTraverser::DONT_TRAVERSE_CURRENT_AND_CHILDREN` can be used instead.
|
||||
visitors from visiting the current node, `NodeVisitor::DONT_TRAVERSE_CURRENT_AND_CHILDREN` can be used instead.
|
||||
|
||||
Both methods can additionally return the value `NodeTraverser::REMOVE_NODE`, in which
|
||||
case the current node will be removed from the parent array. Furthermore, it is possible to return
|
||||
an array of nodes, which will be merged into the parent array at the offset of the current node.
|
||||
I.e. if in `array(A, B, C)` the node `B` should be replaced with `array(X, Y, Z)` the result will
|
||||
be `array(A, X, Y, Z, C)`.
|
||||
Both methods can additionally return the following values:
|
||||
|
||||
* `NodeVisitor::STOP_TRAVERSAL`, in which case no further nodes will be visited.
|
||||
* `NodeVisitor::REMOVE_NODE`, in which case the current node will be removed from the parent array.
|
||||
* `NodeVisitor::REPLACE_WITH_NULL`, in which case the current node will be replaced with `null`.
|
||||
* An array of nodes, which will be merged into the parent array at the offset of the current node.
|
||||
I.e. if in `array(A, B, C)` the node `B` should be replaced with `array(X, Y, Z)` the result will
|
||||
be `array(A, X, Y, Z, C)`.
|
||||
|
||||
Instead of manually implementing the `NodeVisitor` interface you can also extend the `NodeVisitorAbstract`
|
||||
class, which will define empty default implementations for all the above methods.
|
||||
@ -386,7 +395,7 @@ declarations that contains the namespaced name instead of only the shortname tha
|
||||
Example: Converting namespaced code to pseudo namespaces
|
||||
--------------------------------------------------------
|
||||
|
||||
A small example to understand the concept: We want to convert namespaced code to pseudo namespaces
|
||||
A small example to understand the concept: We want to convert namespaced code to pseudo namespaces,
|
||||
so it works on 5.2, i.e. names like `A\\B` should be converted to `A_B`. Note that such conversions
|
||||
are fairly complicated if you take PHP's dynamic features into account, so our conversion will
|
||||
assume that no dynamic features are used.
|
||||
@ -438,7 +447,7 @@ foreach ($files as $file) {
|
||||
}
|
||||
```
|
||||
|
||||
Now lets start with the main code, the `NodeVisitor\NamespaceConverter`. One thing it needs to do
|
||||
Now lets start with the main code, the `NamespaceConverter`. One thing it needs to do
|
||||
is convert `A\\B` style names to `A_B` style ones.
|
||||
|
||||
```php
|
||||
@ -493,7 +502,7 @@ The last thing we need to do is remove the `namespace` and `use` statements:
|
||||
```php
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Stmt;
|
||||
use PhpParser\NodeTraverser;
|
||||
use PhpParser\NodeVisitor;
|
||||
|
||||
class NodeVisitor_NamespaceConverter extends \PhpParser\NodeVisitorAbstract
|
||||
{
|
||||
@ -513,7 +522,7 @@ class NodeVisitor_NamespaceConverter extends \PhpParser\NodeVisitorAbstract
|
||||
return $node->stmts;
|
||||
} elseif ($node instanceof Stmt\Use_) {
|
||||
// remove use nodes altogether
|
||||
return NodeTraverser::REMOVE_NODE;
|
||||
return NodeVisitor::REMOVE_NODE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -27,9 +27,8 @@ Component documentation
|
||||
* [AST builders](component/AST_builders.markdown)
|
||||
* Fluent builders for AST nodes
|
||||
* [Lexer](component/Lexer.markdown)
|
||||
* Lexer options
|
||||
* Token and file positions for nodes
|
||||
* Custom attributes
|
||||
* Emulation
|
||||
* Tokens, positions and attributes
|
||||
* [Error handling](component/Error_handling.markdown)
|
||||
* Column information for errors
|
||||
* Error recovery (parsing of syntactically incorrect code)
|
||||
|
@ -15,9 +15,10 @@ accessed through `getNode()`. Fluent builders are available for
|
||||
the following syntactic elements:
|
||||
|
||||
* namespaces and use statements
|
||||
* classes, interfaces and traits
|
||||
* classes, interfaces, traits and enums
|
||||
* methods, functions and parameters
|
||||
* properties
|
||||
* properties, class constants and enum cases
|
||||
* trait uses and trait use adaptations
|
||||
|
||||
Here is an example:
|
||||
|
||||
@ -95,13 +96,13 @@ abstract class SomeOtherClass extends SomeClass implements A\Few, \Interfaces
|
||||
AnotherTrait::func insteadof SecondTrait;
|
||||
}
|
||||
protected $someProperty;
|
||||
private $anotherProperty = array(1, 2, 3);
|
||||
private $anotherProperty = [1, 2, 3];
|
||||
/**
|
||||
* This method does something.
|
||||
*
|
||||
* @param SomeClass And takes a parameter
|
||||
*/
|
||||
public abstract function someMethod(SomeClass $someParam): bool;
|
||||
abstract public function someMethod(SomeClass $someParam): bool;
|
||||
protected function anotherMethod($someParam = 'test')
|
||||
{
|
||||
print $someParam;
|
||||
@ -133,6 +134,8 @@ nodes. The following methods are currently available:
|
||||
* `propertyFetch($var, $name)`: Creates a property fetch node. Converts `$name` to an `Identifier`
|
||||
node.
|
||||
* `concat(...$exprs)`: Create a tree of `BinaryOp\Concat` nodes for the given expressions.
|
||||
* `attribute($name, $args)`: Create a `Attribute` node. Converts `$name` to a `Name` node and
|
||||
normalizes arguments.
|
||||
|
||||
These methods may be expanded on an as-needed basis. Please open an issue or PR if a common
|
||||
operation is missing.
|
||||
|
@ -70,6 +70,7 @@ expressions, apart from the following:
|
||||
* `Expr\ConstFetch` (only null/false/true are handled)
|
||||
* `Expr\ClassConstFetch`
|
||||
* `Expr\New_` (since PHP 8.1)
|
||||
* `Expr\PropertyFetch` (since PHP 8.2)
|
||||
|
||||
Handling these expression types requires non-local information, such as which global constants are
|
||||
defined. By default, the evaluator will throw a `ConstExprEvaluationException` when it encounters
|
||||
@ -84,7 +85,7 @@ specifying an evaluation fallback function:
|
||||
use PhpParser\{ConstExprEvaluator, ConstExprEvaluationException};
|
||||
use PhpParser\Node\Expr;
|
||||
|
||||
$evalutator = new ConstExprEvaluator(function(Expr $expr) {
|
||||
$evaluator = new ConstExprEvaluator(function(Expr $expr) {
|
||||
if ($expr instanceof Expr\ConstFetch) {
|
||||
return fetchConstantSomehow($expr);
|
||||
}
|
||||
@ -97,7 +98,7 @@ $evalutator = new ConstExprEvaluator(function(Expr $expr) {
|
||||
});
|
||||
|
||||
try {
|
||||
$evalutator->evaluateSilently($someExpr);
|
||||
$evaluator->evaluateSilently($someExpr);
|
||||
} catch (ConstExprEvaluationException $e) {
|
||||
// Handle exception
|
||||
}
|
||||
|
@ -4,29 +4,12 @@ Error handling
|
||||
Errors during parsing or analysis are represented using the `PhpParser\Error` exception class. In addition to an error
|
||||
message, an error can also store additional information about the location the error occurred at.
|
||||
|
||||
How much location information is available depends on the origin of the error and how many lexer attributes have been
|
||||
enabled. At a minimum the start line of the error is usually available.
|
||||
How much location information is available depends on the origin of the error. At a minimum the start line of the error
|
||||
is usually available.
|
||||
|
||||
Column information
|
||||
------------------
|
||||
|
||||
In order to receive information about not only the line, but also the column span an error occurred at, the file
|
||||
position attributes in the lexer need to be enabled:
|
||||
|
||||
```php
|
||||
$lexerOptions = array(
|
||||
'usedAttributes' => array('comments', 'startLine', 'endLine', 'startFilePos', 'endFilePos'),
|
||||
);
|
||||
$parser = (new PhpParser\ParserFactory())->createForHostVersion($lexerOptions);
|
||||
|
||||
try {
|
||||
$stmts = $parser->parse($code);
|
||||
// ...
|
||||
} catch (PhpParser\Error $e) {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
Before using column information, its availability needs to be checked with `$e->hasColumnInfo()`, as the precise
|
||||
location of an error cannot always be determined. The methods for retrieving column information also have to be passed
|
||||
the source code of the parsed file. An example for printing an error:
|
||||
|
@ -16,8 +16,7 @@ use PhpParser\ParserFactory;
|
||||
|
||||
$code = '...';
|
||||
|
||||
$traverser = new NodeTraverser;
|
||||
$traverser->addVisitor(new ParentConnectingVisitor);
|
||||
$traverser = new NodeTraverser(new ParentConnectingVisitor);
|
||||
|
||||
$parser = (new ParserFactory())->createForHostVersion();
|
||||
$ast = $parser->parse($code);
|
||||
@ -39,8 +38,7 @@ use PhpParser\ParserFactory;
|
||||
|
||||
$code = '...';
|
||||
|
||||
$traverser = new NodeTraverser;
|
||||
$traverser->addVisitor(new NodeConnectingVisitor);
|
||||
$traverser = new NodeTraverser(new NodeConnectingVisitor);
|
||||
|
||||
$parser = (new ParserFactory())->createForHostVersion();
|
||||
$ast = $parser->parse($code);
|
||||
|
@ -35,65 +35,6 @@ This will result in the following output (which includes attributes):
|
||||
[
|
||||
{
|
||||
"nodeType": "Stmt_Function",
|
||||
"byRef": false,
|
||||
"name": {
|
||||
"nodeType": "Identifier",
|
||||
"name": "printLine",
|
||||
"attributes": {
|
||||
"startLine": 4,
|
||||
"endLine": 4
|
||||
}
|
||||
},
|
||||
"params": [
|
||||
{
|
||||
"nodeType": "Param",
|
||||
"type": null,
|
||||
"byRef": false,
|
||||
"variadic": false,
|
||||
"var": {
|
||||
"nodeType": "Expr_Variable",
|
||||
"name": "msg",
|
||||
"attributes": {
|
||||
"startLine": 4,
|
||||
"endLine": 4
|
||||
}
|
||||
},
|
||||
"default": null,
|
||||
"attributes": {
|
||||
"startLine": 4,
|
||||
"endLine": 4
|
||||
}
|
||||
}
|
||||
],
|
||||
"returnType": null,
|
||||
"stmts": [
|
||||
{
|
||||
"nodeType": "Stmt_Echo",
|
||||
"exprs": [
|
||||
{
|
||||
"nodeType": "Expr_Variable",
|
||||
"name": "msg",
|
||||
"attributes": {
|
||||
"startLine": 5,
|
||||
"endLine": 5
|
||||
}
|
||||
},
|
||||
{
|
||||
"nodeType": "Scalar_String",
|
||||
"value": "\n",
|
||||
"attributes": {
|
||||
"startLine": 5,
|
||||
"endLine": 5,
|
||||
"kind": 2
|
||||
}
|
||||
}
|
||||
],
|
||||
"attributes": {
|
||||
"startLine": 5,
|
||||
"endLine": 5
|
||||
}
|
||||
}
|
||||
],
|
||||
"attributes": {
|
||||
"startLine": 4,
|
||||
"comments": [
|
||||
@ -101,12 +42,79 @@ This will result in the following output (which includes attributes):
|
||||
"nodeType": "Comment_Doc",
|
||||
"text": "\/** @param string $msg *\/",
|
||||
"line": 3,
|
||||
"filePos": 9,
|
||||
"tokenPos": 2
|
||||
"filePos": 7,
|
||||
"tokenPos": 2,
|
||||
"endLine": 3,
|
||||
"endFilePos": 31,
|
||||
"endTokenPos": 2
|
||||
}
|
||||
],
|
||||
"endLine": 6
|
||||
}
|
||||
},
|
||||
"byRef": false,
|
||||
"name": {
|
||||
"nodeType": "Identifier",
|
||||
"attributes": {
|
||||
"startLine": 4,
|
||||
"endLine": 4
|
||||
},
|
||||
"name": "printLine"
|
||||
},
|
||||
"params": [
|
||||
{
|
||||
"nodeType": "Param",
|
||||
"attributes": {
|
||||
"startLine": 4,
|
||||
"endLine": 4
|
||||
},
|
||||
"type": null,
|
||||
"byRef": false,
|
||||
"variadic": false,
|
||||
"var": {
|
||||
"nodeType": "Expr_Variable",
|
||||
"attributes": {
|
||||
"startLine": 4,
|
||||
"endLine": 4
|
||||
},
|
||||
"name": "msg"
|
||||
},
|
||||
"default": null,
|
||||
"flags": 0,
|
||||
"attrGroups": []
|
||||
}
|
||||
],
|
||||
"returnType": null,
|
||||
"stmts": [
|
||||
{
|
||||
"nodeType": "Stmt_Echo",
|
||||
"attributes": {
|
||||
"startLine": 5,
|
||||
"endLine": 5
|
||||
},
|
||||
"exprs": [
|
||||
{
|
||||
"nodeType": "Expr_Variable",
|
||||
"attributes": {
|
||||
"startLine": 5,
|
||||
"endLine": 5
|
||||
},
|
||||
"name": "msg"
|
||||
},
|
||||
{
|
||||
"nodeType": "Scalar_String",
|
||||
"attributes": {
|
||||
"startLine": 5,
|
||||
"endLine": 5,
|
||||
"kind": 2,
|
||||
"rawValue": "\"\\n\""
|
||||
},
|
||||
"value": "\n"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"attrGroups": [],
|
||||
"namespacedName": null
|
||||
}
|
||||
]
|
||||
```
|
||||
|
@ -1,41 +1,79 @@
|
||||
Lexer component documentation
|
||||
=============================
|
||||
|
||||
The lexer is responsible for providing tokens to the parser. The project comes with two lexers: `PhpParser\Lexer` and
|
||||
`PhpParser\Lexer\Emulative`. The latter is an extension of the former, which adds the ability to emulate tokens of
|
||||
newer PHP versions and thus allows parsing of new code on older versions.
|
||||
The lexer is responsible for providing tokens to the parser. Typical use of the library does not require direct
|
||||
interaction with the lexer, as an appropriate lexer is created by `PhpParser\ParserFactory`. The tokens produced
|
||||
by the lexer can then be retrieved using `PhpParser\Parser::getTokens()`.
|
||||
|
||||
This documentation discusses options available for the default lexers and explains how lexers can be extended.
|
||||
Emulation
|
||||
---------
|
||||
|
||||
Lexer options
|
||||
-------------
|
||||
While this library implements a custom parser, it relies on PHP's `ext/tokenizer` extension to perform lexing. However,
|
||||
this extension only supports lexing code for the PHP version you are running on, while this library also wants to support
|
||||
parsing newer code. For that reason, the lexer performs additional "emulation" in three layers:
|
||||
|
||||
The two default lexers accept an `$options` array in the constructor. Currently only the `'usedAttributes'` option is
|
||||
supported, which allows you to specify which attributes will be added to the AST nodes. The attributes can then be
|
||||
accessed using `$node->getAttribute()`, `$node->setAttribute()`, `$node->hasAttribute()` and `$node->getAttributes()`
|
||||
methods. A sample options array:
|
||||
First, PhpParser uses the `PhpToken` based representation introduced in PHP 8.0, rather than the array-based tokens from
|
||||
previous versions. The `PhpParser\Token` class either extends `PhpToken` (on PHP 8.0) or a polyfill implementation. The
|
||||
polyfill implementation will also perform two emulations that are required by the parser and cannot be disabled:
|
||||
|
||||
* Single-line comments use the PHP 8.0 representation that does not include a trailing newline. The newline will be
|
||||
part of a following `T_WHITESPACE` token.
|
||||
* Namespaced names use the PHP 8.0 representation using `T_NAME_FULLY_QUALIFIED`, `T_NAME_QUALIFIED` and
|
||||
`T_NAME_RELATIVE` tokens, rather than the previous representation using a sequence of `T_STRING` and `T_NS_SEPARATOR`.
|
||||
This means that certain code that is legal on older versions (namespaced names including whitespace, such as `A \ B`)
|
||||
will not be accepted by the parser.
|
||||
|
||||
Second, the `PhpParser\Lexer` base class will convert `&` tokens into the PHP 8.1 representation of either
|
||||
`T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG` or `T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG`. This is required by the parser
|
||||
and cannot be disabled.
|
||||
|
||||
Finally, `PhpParser\Lexer\Emulative` performs other, optional emulations. This lexer is parameterized by `PhpVersion`
|
||||
and will try to emulate `ext/tokenizer` output for that version. This is done using separate `TokenEmulator`s for each
|
||||
emulated feature.
|
||||
|
||||
Emulation is usually used to support newer PHP versions, but there is also very limited support for reverse emulation to
|
||||
older PHP versions, which can make keywords from newer versions non-reserved.
|
||||
|
||||
Tokens, positions and attributes
|
||||
--------------------------------
|
||||
|
||||
The `Lexer::tokenize()` method returns an array of `PhpParser\Token`s. The most important parts of the interface can be
|
||||
summarized as follows:
|
||||
|
||||
```php
|
||||
$lexer = new PhpParser\Lexer(array(
|
||||
'usedAttributes' => array(
|
||||
'comments', 'startLine', 'endLine'
|
||||
)
|
||||
));
|
||||
class Token {
|
||||
/** @var int Token ID, either T_* or ord($char) for single-character tokens. */
|
||||
public int $id;
|
||||
/** @var string The textual content of the token. */
|
||||
public string $text;
|
||||
/** @var int The 1-based starting line of the token (or -1 if unknown). */
|
||||
public int $line;
|
||||
/** @var int The 0-based starting position of the token (or -1 if unknown). */
|
||||
public int $pos;
|
||||
|
||||
/** @param int|string|(int|string)[] $kind Token ID or text (or array of them) */
|
||||
public function is($kind): bool;
|
||||
}
|
||||
```
|
||||
|
||||
The attributes used in this example match the default behavior of the lexer. The following attributes are supported:
|
||||
Unlike PHP's own `PhpToken::tokenize()` output, the token array is terminated by a sentinel token with ID 0.
|
||||
|
||||
* `comments`: Array of `PhpParser\Comment` or `PhpParser\Comment\Doc` instances, representing all comments that occurred
|
||||
between the previous non-discarded token and the current one. Use of this attribute is required for the
|
||||
`$node->getComments()` and `$node->getDocComment()` methods to work. The attribute is also needed if you wish the pretty
|
||||
printer to retain comments present in the original code.
|
||||
* `startLine`: Line in which the node starts. This attribute is required for the `$node->getLine()` to work. It is also
|
||||
required if syntax errors should contain line number information.
|
||||
* `endLine`: Line in which the node ends. Required for `$node->getEndLine()`.
|
||||
* `startTokenPos`: Offset into the token array of the first token in the node. Required for `$node->getStartTokenPos()`.
|
||||
* `endTokenPos`: Offset into the token array of the last token in the node. Required for `$node->getEndTokenPos()`.
|
||||
* `startFilePos`: Offset into the code string of the first character that is part of the node. Required for `$node->getStartFilePos()`.
|
||||
* `endFilePos`: Offset into the code string of the last character that is part of the node. Required for `$node->getEndFilePos()`.
|
||||
The lexer is normally invoked implicitly by the parser. In that case, the tokens for the last parse can be retrieved
|
||||
using `Parser::getTokens()`.
|
||||
|
||||
Nodes in the AST produced by the parser always corresponds to some range of tokens. The parser adds a number of
|
||||
positioning attributes to allow mapping nodes back to lines, tokens or file offsets:
|
||||
|
||||
* `startLine`: Line in which the node starts. Used by `$node->getStartLine()`.
|
||||
* `endLine`: Line in which the node ends. Used by `$node->getEndLine()`.
|
||||
* `startTokenPos`: Offset into the token array of the first token in the node. Used by `$node->getStartTokenPos()`.
|
||||
* `endTokenPos`: Offset into the token array of the last token in the node. Used by `$node->getEndTokenPos()`.
|
||||
* `startFilePos`: Offset into the code string of the first character that is part of the node. Used by `$node->getStartFilePos()`.
|
||||
* `endFilePos`: Offset into the code string of the last character that is part of the node. Used by `$node->getEndFilePos()`.
|
||||
|
||||
Note that `start`/`end` here are closed rather than half-open ranges. This means that a node consisting of a single
|
||||
token will have `startTokenPos == endTokenPos` rather than `startTokenPos + 1 == endTokenPos`. This also means that a
|
||||
zero-length node will have `startTokenPos -1 == endTokenPos`.
|
||||
|
||||
### Using token positions
|
||||
|
||||
@ -73,89 +111,16 @@ class MyNodeVisitor extends PhpParser\NodeVisitorAbstract {
|
||||
}
|
||||
}
|
||||
|
||||
$lexerOptions = array(
|
||||
'usedAttributes' => array(
|
||||
'comments', 'startLine', 'endLine', 'startTokenPos', 'endTokenPos'
|
||||
)
|
||||
);
|
||||
$parser = (new PhpParser\ParserFactory())->createForHostVersion($lexerOptions);
|
||||
|
||||
$visitor = new MyNodeVisitor();
|
||||
$traverser = new PhpParser\NodeTraverser();
|
||||
$traverser->addVisitor($visitor);
|
||||
$traverser = new PhpParser\NodeTraverser($visitor);
|
||||
|
||||
try {
|
||||
$stmts = $parser->parse($code);
|
||||
$visitor->setTokens($lexer->getTokens());
|
||||
$visitor->setTokens($parser->getTokens());
|
||||
$stmts = $traverser->traverse($stmts);
|
||||
} catch (PhpParser\Error $e) {
|
||||
echo 'Parse Error: ', $e->getMessage();
|
||||
}
|
||||
```
|
||||
|
||||
The same approach can also be used to perform specific modifications in the code, without changing the formatting in
|
||||
other places (which is the case when using the pretty printer).
|
||||
|
||||
Lexer extension
|
||||
---------------
|
||||
|
||||
A lexer has to define the following public interface:
|
||||
|
||||
```php
|
||||
function startLexing(string $code, ErrorHandler $errorHandler = null): void;
|
||||
function getTokens(): array;
|
||||
function handleHaltCompiler(): string;
|
||||
function getNextToken(string &$value = null, array &$startAttributes = null, array &$endAttributes = null): int;
|
||||
```
|
||||
|
||||
The `startLexing()` method is invoked whenever the `parse()` method of the parser is called and is passed the source
|
||||
code that is to be lexed (including the opening tag). It can be used to reset state or preprocess the source code or tokens. The
|
||||
passed `ErrorHandler` should be used to report lexing errors.
|
||||
|
||||
The `getTokens()` method returns the current array of `PhpParser\Token`s, which are compatible with the PHP 8 `PhpToken`
|
||||
class. This method is not used by the parser (which uses `getNextToken()`), but is useful in combination with the token
|
||||
position attributes.
|
||||
|
||||
The `handleHaltCompiler()` method is called whenever a `T_HALT_COMPILER` token is encountered. It has to return the
|
||||
remaining string after the construct (not including `();`).
|
||||
|
||||
The `getNextToken()` method returns the ID of the next token (in the sense of `Token::$id`). If no more
|
||||
tokens are available it must return `0`, which is the ID of the `EOF` token. Furthermore, the string content of the
|
||||
token should be written into the by-reference `$value` parameter (which will then be available as `$n` in the parser).
|
||||
|
||||
### Attribute handling
|
||||
|
||||
The other two by-ref variables `$startAttributes` and `$endAttributes` define which attributes will eventually be
|
||||
assigned to the generated nodes: The parser will take the `$startAttributes` from the first token which is part of the
|
||||
node and the `$endAttributes` from the last token that is part of the node.
|
||||
|
||||
E.g. if the tokens `T_FUNCTION T_STRING ... '{' ... '}'` constitute a node, then the `$startAttributes` from the
|
||||
`T_FUNCTION` token will be taken and the `$endAttributes` from the `'}'` token.
|
||||
|
||||
An application of custom attributes is storing the exact original formatting of literals: While the parser does retain
|
||||
some information about the formatting of integers (like decimal vs. hexadecimal) or strings (like used quote type), it
|
||||
does not preserve the exact original formatting (e.g. leading zeros for integers or escape sequences in strings). This
|
||||
can be remedied by storing the original value in an attribute:
|
||||
|
||||
```php
|
||||
use PhpParser\Lexer;
|
||||
use PhpParser\Parser\Tokens;
|
||||
|
||||
class KeepOriginalValueLexer extends Lexer // or Lexer\Emulative
|
||||
{
|
||||
public function getNextToken(&$value = null, &$startAttributes = null, &$endAttributes = null) {
|
||||
$tokenId = parent::getNextToken($value, $startAttributes, $endAttributes);
|
||||
|
||||
if ($tokenId == \T_CONSTANT_ENCAPSED_STRING // non-interpolated string
|
||||
|| $tokenId == \T_ENCAPSED_AND_WHITESPACE // interpolated string
|
||||
|| $tokenId == \T_LNUMBER // integer
|
||||
|| $tokenId == \T_DNUMBER // floating point number
|
||||
) {
|
||||
// could also use $startAttributes, doesn't really matter here
|
||||
$endAttributes['originalValue'] = $value;
|
||||
}
|
||||
|
||||
return $tokenId;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
@ -24,7 +24,7 @@ $stmts = $nodeTraverser->traverse($stmts);
|
||||
|
||||
In the default configuration, the name resolver will perform three actions:
|
||||
|
||||
* Declarations of functions, classes, interfaces, traits and global constants will have a
|
||||
* Declarations of functions, classes, interfaces, traits, enums and global constants will have a
|
||||
`namespacedName` property added, which contains the function/class/etc name including the
|
||||
namespace prefix. For historic reasons this is a **property** rather than an attribute.
|
||||
* Names will be replaced by fully qualified resolved names, which are instances of
|
||||
@ -32,7 +32,7 @@ In the default configuration, the name resolver will perform three actions:
|
||||
* Unqualified function and constant names inside a namespace cannot be statically resolved. Inside
|
||||
a namespace `Foo`, a call to `strlen()` may either refer to the namespaced `\Foo\strlen()`, or
|
||||
the global `\strlen()`. Because PHP-Parser does not have the necessary context to decide this,
|
||||
such names are left unresolved. Additionally a `namespacedName` **attribute** is added to the
|
||||
such names are left unresolved. Additionally, a `namespacedName` **attribute** is added to the
|
||||
name node.
|
||||
|
||||
The name resolver accepts an option array as the second argument, with the following default values:
|
||||
@ -47,7 +47,7 @@ $nameResolver = new PhpParser\NodeVisitor\NameResolver(null, [
|
||||
If the `preserveOriginalNames` option is enabled, then the resolved (fully qualified) name will have
|
||||
an `originalName` attribute, which contains the unresolved name.
|
||||
|
||||
If the `replaceNodes` option is disabled, then names will no longer be resolved in-place. Instead a
|
||||
If the `replaceNodes` option is disabled, then names will no longer be resolved in-place. Instead, a
|
||||
`resolvedName` attribute will be added to each name, which contains the resolved (fully qualified)
|
||||
name. Once again, if an unqualified function or constant name cannot be resolved, then the
|
||||
`resolvedName` attribute will not be present, and instead a `namespacedName` attribute is added.
|
||||
@ -75,7 +75,7 @@ class NameContext {
|
||||
}
|
||||
```
|
||||
|
||||
The `$type` parameters accept on of the `Stmt\Use_::TYPE_*` constants, which represent the three
|
||||
The `$type` parameters accept one of the `Stmt\Use_::TYPE_*` constants, which represent the three
|
||||
basic symbol types in PHP (functions, constants and everything else).
|
||||
|
||||
Next to name resolution, the `NameContext` also supports the reverse operation of finding a short
|
||||
|
@ -40,26 +40,5 @@ parse multiple files.
|
||||
|
||||
When possible, objects should be reused rather than being newly instantiated for every use. Some
|
||||
objects have expensive initialization procedures, which will be unnecessarily repeated if the object
|
||||
is not reused. (Currently two objects with particularly expensive setup are lexers and pretty
|
||||
is not reused. (Currently two objects with particularly expensive setup are parsers and pretty
|
||||
printers, though the details might change between versions of this library.)
|
||||
|
||||
Garbage collection
|
||||
------------------
|
||||
|
||||
A limitation in PHP's cyclic garbage collector may lead to major performance degradation when the
|
||||
active working set exceeds 10000 objects (or arrays). Especially when parsing very large files this
|
||||
limit is significantly exceeded and PHP will spend the majority of time performing unnecessary
|
||||
garbage collection attempts.
|
||||
|
||||
Without GC, parsing time is roughly linear in the input size. With GC, this degenerates to quadratic
|
||||
runtime for large files. While the specifics may differ, as a rough guideline you may expect a 2.5x
|
||||
GC overhead for 500KB files and a 5x overhead for 1MB files.
|
||||
|
||||
Because this a limitation in PHP's implementation, there is no easy way to work around this. If
|
||||
possible, you should avoid parsing very large files, as they will impact overall execution time
|
||||
disproportionally (and are usually generated anyway).
|
||||
|
||||
Of course, you can also try to (temporarily) disable GC. By design the AST generated by PHP-Parser
|
||||
is cycle-free, so the AST itself will never cause leaks with GC disabled. However, other code
|
||||
(including for example the parser object itself) may hold cycles, so disabling of GC should be
|
||||
approached with care.
|
||||
|
@ -31,16 +31,30 @@ expression.
|
||||
Customizing the formatting
|
||||
--------------------------
|
||||
|
||||
The pretty printer respects a number of `kind` attributes used by some notes (e.g., whether an
|
||||
integer should be printed as decimal, hexadecimal, etc). Additionally, it supports two options:
|
||||
The pretty printer respects a number of `kind` attributes used by some nodes (e.g., whether an
|
||||
integer should be printed as decimal, hexadecimal, etc). Additionally, it supports three options:
|
||||
|
||||
* `phpVersion` (defaults to 7.0) 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.
|
||||
* `newline` (defaults to `"\n"`) can be set to `"\r\n"` in order to produce Windows newlines.
|
||||
* `indent` (defaults to four spaces `" "`) can be set to any number of spaces or a single tab.
|
||||
* `shortArraySyntax` determines the used array syntax if the `kind` attribute is not set. This is
|
||||
a legacy option, and `phpVersion` should be used to control this behavior instead.
|
||||
|
||||
However, the default pretty printer does not provide any functionality for fine-grained
|
||||
customization of code formatting.
|
||||
The behaviors controlled by `phpVersion` (defaults to PHP 7.4) are:
|
||||
|
||||
* For PHP >= 7.0, short array syntax `[]` will be used by default. This does not affect nodes that
|
||||
specify an explicit array syntax using the `kind` attribute.
|
||||
* For PHP >= 7.0, parentheses around `yield` expressions will only be printed when necessary.
|
||||
* For PHP >= 7.1, the short array syntax `[]` will be used for destructuring by default (instead of
|
||||
`list()`). This does not affect nodes that specify and explicit syntax using the `kind` attribute.
|
||||
* For PHP >= 7.3, a newline is no longer forced after heredoc/nowdoc strings, as the requirement
|
||||
for this has been removed with the introduction of flexible heredoc/nowdoc strings.
|
||||
* For PHP >= 7.3, heredoc/nowdoc strings are indented just like regular code. This was allowed with
|
||||
the introduction of flexible heredoc/nowdoc strings.
|
||||
|
||||
The default pretty printer does not provide functionality for fine-grained customization of code
|
||||
formatting.
|
||||
|
||||
If you want to make minor changes to the formatting, the easiest way is to extend the pretty printer
|
||||
and override the methods responsible for the node types you are interested in.
|
||||
@ -63,29 +77,19 @@ code which has been modified or newly inserted.
|
||||
Use of the formatting-preservation functionality requires some additional preparatory steps:
|
||||
|
||||
```php
|
||||
use PhpParser\{Lexer, NodeTraverser, NodeVisitor, ParserFactory, PrettyPrinter};
|
||||
|
||||
$lexerOptions = new [
|
||||
'usedAttributes' => [
|
||||
'comments',
|
||||
'startLine', 'endLine',
|
||||
'startTokenPos', 'endTokenPos',
|
||||
],
|
||||
];
|
||||
$parser = (new ParserFactory())->createForHostVersion($lexerOptions);
|
||||
|
||||
$traverser = new NodeTraverser();
|
||||
$traverser->addVisitor(new NodeVisitor\CloningVisitor());
|
||||
|
||||
$printer = new PrettyPrinter\Standard();
|
||||
use PhpParser\{NodeTraverser, NodeVisitor, ParserFactory, PrettyPrinter};
|
||||
|
||||
$parser = (new ParserFactory())->createForHostVersion();
|
||||
$oldStmts = $parser->parse($code);
|
||||
$oldTokens = $parser->getLexer()->getTokens();
|
||||
$oldTokens = $parser->getTokens();
|
||||
|
||||
// Run CloningVisitor before making changes to the AST.
|
||||
$traverser = new NodeTraverser(new NodeVisitor\CloningVisitor());
|
||||
$newStmts = $traverser->traverse($oldStmts);
|
||||
|
||||
// MODIFY $newStmts HERE
|
||||
|
||||
$printer = new PrettyPrinter\Standard();
|
||||
$newCode = $printer->printFormatPreserving($newStmts, $oldStmts, $oldTokens);
|
||||
```
|
||||
|
||||
@ -94,5 +98,5 @@ If you make use of the name resolution functionality, you will likely want to di
|
||||
the AST and causing spurious changes to the pretty printed code. For more information, see the
|
||||
[name resolution documentation](Name_resolution.markdown).
|
||||
|
||||
The formatting-preservation works on a best-effort basis and may sometimes reformat more code tha
|
||||
The formatting-preservation works on a best-effort basis and may sometimes reformat more code than
|
||||
necessary. If you encounter problems while using this functionality, please open an issue.
|
||||
|
@ -21,6 +21,18 @@ $stmts = ...;
|
||||
$modifiedStmts = $traverser->traverse($stmts);
|
||||
```
|
||||
|
||||
Visitors can be either passed to the `NodeTraverser` constructor, or added using `addVisitor()`:
|
||||
|
||||
```php
|
||||
$traverser = new NodeTraverser($visitor1, $visitor2, $visitor3);
|
||||
|
||||
// Equivalent to:
|
||||
$traverser = new NodeTraverser();
|
||||
$traverser->addVisitor($visitor1);
|
||||
$traverser->addVisitor($visitor2);
|
||||
$traverser->addVisitor($visitor3);
|
||||
```
|
||||
|
||||
Node visitors
|
||||
-------------
|
||||
|
||||
@ -47,20 +59,19 @@ For example, if we have the following excerpt of an AST
|
||||
|
||||
```
|
||||
Expr_FuncCall(
|
||||
name: Name(
|
||||
parts: array(
|
||||
0: printLine
|
||||
)
|
||||
)
|
||||
args: array(
|
||||
0: Arg(
|
||||
value: Scalar_String(
|
||||
value: Hello World!!!
|
||||
)
|
||||
byRef: false
|
||||
unpack: false
|
||||
)
|
||||
)
|
||||
name: Name(
|
||||
name: printLine
|
||||
)
|
||||
args: array(
|
||||
0: Arg(
|
||||
name: null
|
||||
value: Scalar_String(
|
||||
value: Hello World!!!
|
||||
)
|
||||
byRef: false
|
||||
unpack: false
|
||||
)
|
||||
)
|
||||
)
|
||||
```
|
||||
|
||||
@ -129,13 +140,13 @@ Now `$a && $b` will be replaced by `!($a && $b)`. Then the traverser will go int
|
||||
only) child of `!($a && $b)`, which is `$a && $b`. The transformation applies again and we end up
|
||||
with `!!($a && $b)`. This will continue until PHP hits the memory limit.
|
||||
|
||||
Finally, there are two special replacement types. The first is removal of a node:
|
||||
Finally, there are three special replacement types. The first is removal of a node:
|
||||
|
||||
```php
|
||||
public function leaveNode(Node $node) {
|
||||
if ($node instanceof Node\Stmt\Return_) {
|
||||
// Remove all return statements
|
||||
return NodeTraverser::REMOVE_NODE;
|
||||
return NodeVisitor::REMOVE_NODE;
|
||||
}
|
||||
}
|
||||
```
|
||||
@ -155,7 +166,7 @@ public function leaveNode(Node $node) {
|
||||
&& $node->expr->name instanceof Node\Name
|
||||
&& $node->expr->name->toString() === 'var_dump'
|
||||
) {
|
||||
return NodeTraverser::REMOVE_NODE;
|
||||
return NodeVisitor::REMOVE_NODE;
|
||||
}
|
||||
}
|
||||
```
|
||||
@ -164,6 +175,20 @@ This example will remove all calls to `var_dump()` which occur as expression sta
|
||||
that `var_dump($a);` will be removed, but `if (var_dump($a))` will not be removed (and there is no
|
||||
obvious way in which it can be removed).
|
||||
|
||||
Another way to remove nodes is to replace them with `null`. For example, all `else` statements could
|
||||
be removed as follows:
|
||||
|
||||
```php
|
||||
public function leaveNode(Node $node) {
|
||||
if ($node instanceof Node\Stmt\Else_) {
|
||||
return NodeVisitor::REPLACE_WITH_NULL;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This is only safe to do if the subnode the node is stored in is nullable. `Node\Stmt\Else_` only
|
||||
occurs inside `Node\Stmt\If_::$else`, which is nullable, so this particular replacement is safe.
|
||||
|
||||
Next to removing nodes, it is also possible to replace one node with multiple nodes. This
|
||||
only works if the parent structure is an array.
|
||||
|
||||
@ -197,7 +222,7 @@ private $classes = [];
|
||||
public function enterNode(Node $node) {
|
||||
if ($node instanceof Node\Stmt\Class_) {
|
||||
$this->classes[] = $node;
|
||||
return NodeTraverser::DONT_TRAVERSE_CHILDREN;
|
||||
return NodeVisitor::DONT_TRAVERSE_CHILDREN;
|
||||
}
|
||||
}
|
||||
```
|
||||
@ -217,7 +242,7 @@ public function enterNode(Node $node) {
|
||||
$node->namespacedName->toString() === 'Foo\Bar\Baz'
|
||||
) {
|
||||
$this->class = $node;
|
||||
return NodeTraverser::STOP_TRAVERSAL;
|
||||
return NodeVisitor::STOP_TRAVERSAL;
|
||||
}
|
||||
}
|
||||
```
|
||||
@ -255,13 +280,14 @@ $visitorA->enterNode(Stmt_Return)
|
||||
$visitorB->enterNode(Stmt_Return)
|
||||
$visitorA->enterNode(Expr_Variable)
|
||||
$visitorB->enterNode(Expr_Variable)
|
||||
$visitorA->leaveNode(Expr_Variable)
|
||||
$visitorB->leaveNode(Expr_Variable)
|
||||
$visitorA->leaveNode(Stmt_Return)
|
||||
$visitorA->leaveNode(Expr_Variable)
|
||||
$visitorB->leaveNode(Stmt_Return)
|
||||
$visitorA->leaveNode(Stmt_Return)
|
||||
```
|
||||
|
||||
That is, when visiting a node, enterNode and leaveNode will always be called for all visitors.
|
||||
That is, when visiting a node, `enterNode()` and `leaveNode()` will always be called for all
|
||||
visitors, with the `leaveNode()` calls happening in the reverse order of the `enterNode()` calls.
|
||||
Running multiple visitors in parallel improves performance, as the AST only has to be traversed
|
||||
once. However, it is not always possible to write visitors in a way that allows interleaved
|
||||
execution. In this case, you can always fall back to performing multiple traversals:
|
||||
@ -286,6 +312,7 @@ special enterNode/leaveNode return values:
|
||||
* If a visitor returns a replacement node, subsequent visitors will be passed the replacement node,
|
||||
not the original one.
|
||||
* If a visitor returns `REMOVE_NODE`, subsequent visitors will not see this node.
|
||||
* If a visitor returns `REPLACE_WITH_NULL`, subsequent visitors will not see this node.
|
||||
* If a visitor returns an array of replacement nodes, subsequent visitors will see neither the node
|
||||
that was replaced, nor the replacement nodes.
|
||||
|
||||
@ -332,5 +359,6 @@ be accessed: From parents to children. However, it can often be convenient to op
|
||||
reverse direction: When working on a node, you might want to check if the parent node satisfies a
|
||||
certain property.
|
||||
|
||||
PHP-Parser does not add parent (or sibling) references to nodes by itself, but you can easily
|
||||
emulate this with a visitor. See the [FAQ](FAQ.markdown) for more information.
|
||||
PHP-Parser does not add parent (or sibling) references to nodes by default, but you can enable them
|
||||
using the `ParentConnectingVisitor` or `NodeConnectingVisitor`. See the [FAQ](FAQ.markdown) for
|
||||
more information.
|
||||
|
@ -1,7 +1,7 @@
|
||||
<?php declare(strict_types=1);
|
||||
$meta #
|
||||
#semval($) $this->semValue
|
||||
#semval($,%t) $this->semValue
|
||||
#semval($) $self->semValue
|
||||
#semval($,%t) $self->semValue
|
||||
#semval(%n) $stackPos-(%l-%n)
|
||||
#semval(%n,%t) $stackPos-(%l-%n)
|
||||
|
||||
@ -28,68 +28,68 @@ class #(-p) extends \PhpParser\ParserAbstract
|
||||
public const %s = %n;
|
||||
#endtokenval
|
||||
|
||||
protected $tokenToSymbolMapSize = #(YYMAXLEX);
|
||||
protected $actionTableSize = #(YYLAST);
|
||||
protected $gotoTableSize = #(YYGLAST);
|
||||
protected int $tokenToSymbolMapSize = #(YYMAXLEX);
|
||||
protected int $actionTableSize = #(YYLAST);
|
||||
protected int $gotoTableSize = #(YYGLAST);
|
||||
|
||||
protected $invalidSymbol = #(YYBADCH);
|
||||
protected $errorSymbol = #(YYINTERRTOK);
|
||||
protected $defaultAction = #(YYDEFAULT);
|
||||
protected $unexpectedTokenRule = #(YYUNEXPECTED);
|
||||
protected int $invalidSymbol = #(YYBADCH);
|
||||
protected int $errorSymbol = #(YYINTERRTOK);
|
||||
protected int $defaultAction = #(YYDEFAULT);
|
||||
protected int $unexpectedTokenRule = #(YYUNEXPECTED);
|
||||
|
||||
protected $YY2TBLSTATE = #(YY2TBLSTATE);
|
||||
protected $numNonLeafStates = #(YYNLSTATES);
|
||||
protected int $YY2TBLSTATE = #(YY2TBLSTATE);
|
||||
protected int $numNonLeafStates = #(YYNLSTATES);
|
||||
|
||||
protected $symbolToName = array(
|
||||
protected array $symbolToName = array(
|
||||
#listvar terminals
|
||||
);
|
||||
|
||||
protected $tokenToSymbol = array(
|
||||
protected array $tokenToSymbol = array(
|
||||
#listvar yytranslate
|
||||
);
|
||||
|
||||
protected $action = array(
|
||||
protected array $action = array(
|
||||
#listvar yyaction
|
||||
);
|
||||
|
||||
protected $actionCheck = array(
|
||||
protected array $actionCheck = array(
|
||||
#listvar yycheck
|
||||
);
|
||||
|
||||
protected $actionBase = array(
|
||||
protected array $actionBase = array(
|
||||
#listvar yybase
|
||||
);
|
||||
|
||||
protected $actionDefault = array(
|
||||
protected array $actionDefault = array(
|
||||
#listvar yydefault
|
||||
);
|
||||
|
||||
protected $goto = array(
|
||||
protected array $goto = array(
|
||||
#listvar yygoto
|
||||
);
|
||||
|
||||
protected $gotoCheck = array(
|
||||
protected array $gotoCheck = array(
|
||||
#listvar yygcheck
|
||||
);
|
||||
|
||||
protected $gotoBase = array(
|
||||
protected array $gotoBase = array(
|
||||
#listvar yygbase
|
||||
);
|
||||
|
||||
protected $gotoDefault = array(
|
||||
protected array $gotoDefault = array(
|
||||
#listvar yygdefault
|
||||
);
|
||||
|
||||
protected $ruleToNonTerminal = array(
|
||||
protected array $ruleToNonTerminal = array(
|
||||
#listvar yylhs
|
||||
);
|
||||
|
||||
protected $ruleToLength = array(
|
||||
protected array $ruleToLength = array(
|
||||
#listvar yylen
|
||||
);
|
||||
#if -t
|
||||
|
||||
protected $productions = array(
|
||||
protected array $productions = array(
|
||||
#production-strings;
|
||||
);
|
||||
#endif
|
||||
@ -97,13 +97,11 @@ class #(-p) extends \PhpParser\ParserAbstract
|
||||
protected function initReduceCallbacks(): void {
|
||||
$this->reduceCallbacks = [
|
||||
#reduce
|
||||
%n => function ($stackPos) {
|
||||
%n => static function ($self, $stackPos) {
|
||||
%b
|
||||
},
|
||||
#noact
|
||||
%n => function ($stackPos) {
|
||||
$this->semValue = $this->semStack[$stackPos];
|
||||
},
|
||||
%n => null,
|
||||
#endreduce
|
||||
];
|
||||
}
|
||||
|
377
grammar/php.y
377
grammar/php.y
@ -81,7 +81,10 @@
|
||||
%token T_USE
|
||||
%token T_INSTEADOF
|
||||
%token T_GLOBAL
|
||||
%right T_STATIC T_ABSTRACT T_FINAL T_PRIVATE T_PROTECTED T_PUBLIC T_READONLY
|
||||
%token T_STATIC T_ABSTRACT T_FINAL T_PRIVATE T_PROTECTED T_PUBLIC T_READONLY
|
||||
%token T_PUBLIC_SET
|
||||
%token T_PROTECTED_SET
|
||||
%token T_PRIVATE_SET
|
||||
%token T_VAR
|
||||
%token T_UNSET
|
||||
%token T_ISSET
|
||||
@ -103,6 +106,7 @@
|
||||
%token T_TRAIT_C
|
||||
%token T_METHOD_C
|
||||
%token T_FUNC_C
|
||||
%token T_PROPERTY_C
|
||||
%token T_LINE
|
||||
%token T_FILE
|
||||
%token T_START_HEREDOC
|
||||
@ -134,7 +138,7 @@ top_statement_list_ex:
|
||||
|
||||
top_statement_list:
|
||||
top_statement_list_ex
|
||||
{ makeZeroLengthNop($nop, $this->lookaheadStartAttributes);
|
||||
{ makeZeroLengthNop($nop);
|
||||
if ($nop !== null) { $1[] = $nop; } $$ = $1; }
|
||||
;
|
||||
|
||||
@ -185,7 +189,7 @@ namespace_name:
|
||||
;
|
||||
|
||||
legacy_namespace_name:
|
||||
namespace_name { $$ = $1; }
|
||||
namespace_name
|
||||
| T_NAME_FULLY_QUALIFIED { $$ = Name[substr($1, 1)]; }
|
||||
;
|
||||
|
||||
@ -229,15 +233,15 @@ attributes:
|
||||
|
||||
optional_attributes:
|
||||
/* empty */ { $$ = []; }
|
||||
| attributes { $$ = $1; }
|
||||
| attributes
|
||||
;
|
||||
|
||||
top_statement:
|
||||
statement { $$ = $1; }
|
||||
| function_declaration_statement { $$ = $1; }
|
||||
| class_declaration_statement { $$ = $1; }
|
||||
statement
|
||||
| function_declaration_statement
|
||||
| class_declaration_statement
|
||||
| T_HALT_COMPILER '(' ')' ';'
|
||||
{ $$ = Stmt\HaltCompiler[$this->lexer->handleHaltCompiler()]; }
|
||||
{ $$ = Stmt\HaltCompiler[$this->handleHaltCompiler()]; }
|
||||
| T_NAMESPACE namespace_declaration_name semi
|
||||
{ $$ = Stmt\Namespace_[$2, null];
|
||||
$$->setAttribute('kind', Stmt\Namespace_::KIND_SEMICOLON);
|
||||
@ -252,7 +256,7 @@ top_statement:
|
||||
$this->checkNamespace($$); }
|
||||
| T_USE use_declarations semi { $$ = Stmt\Use_[$2, Stmt\Use_::TYPE_NORMAL]; }
|
||||
| T_USE use_type use_declarations semi { $$ = Stmt\Use_[$3, $2]; }
|
||||
| group_use_declaration semi { $$ = $1; }
|
||||
| group_use_declaration
|
||||
| T_CONST constant_declaration_list semi { $$ = Stmt\Const_[$2]; }
|
||||
;
|
||||
|
||||
@ -262,14 +266,14 @@ use_type:
|
||||
;
|
||||
|
||||
group_use_declaration:
|
||||
T_USE use_type legacy_namespace_name T_NS_SEPARATOR '{' unprefixed_use_declarations '}'
|
||||
T_USE use_type legacy_namespace_name T_NS_SEPARATOR '{' unprefixed_use_declarations '}' semi
|
||||
{ $$ = Stmt\GroupUse[$3, $6, $2]; }
|
||||
| T_USE legacy_namespace_name T_NS_SEPARATOR '{' inline_use_declarations '}'
|
||||
| T_USE legacy_namespace_name T_NS_SEPARATOR '{' inline_use_declarations '}' semi
|
||||
{ $$ = Stmt\GroupUse[$2, $5, Stmt\Use_::TYPE_UNKNOWN]; }
|
||||
;
|
||||
|
||||
unprefixed_use_declarations:
|
||||
non_empty_unprefixed_use_declarations optional_comma { $$ = $1; }
|
||||
non_empty_unprefixed_use_declarations optional_comma
|
||||
;
|
||||
|
||||
non_empty_unprefixed_use_declarations:
|
||||
@ -279,7 +283,7 @@ non_empty_unprefixed_use_declarations:
|
||||
;
|
||||
|
||||
use_declarations:
|
||||
non_empty_use_declarations no_comma { $$ = $1; }
|
||||
non_empty_use_declarations no_comma
|
||||
;
|
||||
|
||||
non_empty_use_declarations:
|
||||
@ -288,7 +292,7 @@ non_empty_use_declarations:
|
||||
;
|
||||
|
||||
inline_use_declarations:
|
||||
non_empty_inline_use_declarations optional_comma { $$ = $1; }
|
||||
non_empty_inline_use_declarations optional_comma
|
||||
;
|
||||
|
||||
non_empty_inline_use_declarations:
|
||||
@ -317,7 +321,7 @@ inline_use_declaration:
|
||||
;
|
||||
|
||||
constant_declaration_list:
|
||||
non_empty_constant_declaration_list no_comma { $$ = $1; }
|
||||
non_empty_constant_declaration_list no_comma
|
||||
;
|
||||
|
||||
non_empty_constant_declaration_list:
|
||||
@ -331,7 +335,7 @@ constant_declaration:
|
||||
;
|
||||
|
||||
class_const_list:
|
||||
non_empty_class_const_list no_comma { $$ = $1; }
|
||||
non_empty_class_const_list no_comma
|
||||
;
|
||||
|
||||
non_empty_class_const_list:
|
||||
@ -340,7 +344,10 @@ non_empty_class_const_list:
|
||||
;
|
||||
|
||||
class_const:
|
||||
identifier_maybe_reserved '=' expr { $$ = Node\Const_[$1, $3]; }
|
||||
T_STRING '=' expr
|
||||
{ $$ = Node\Const_[new Node\Identifier($1, stackAttributes(#1)), $3]; }
|
||||
| semi_reserved '=' expr
|
||||
{ $$ = Node\Const_[new Node\Identifier($1, stackAttributes(#1)), $3]; }
|
||||
;
|
||||
|
||||
inner_statement_list_ex:
|
||||
@ -350,34 +357,26 @@ inner_statement_list_ex:
|
||||
|
||||
inner_statement_list:
|
||||
inner_statement_list_ex
|
||||
{ makeZeroLengthNop($nop, $this->lookaheadStartAttributes);
|
||||
{ makeZeroLengthNop($nop);
|
||||
if ($nop !== null) { $1[] = $nop; } $$ = $1; }
|
||||
;
|
||||
|
||||
inner_statement:
|
||||
statement { $$ = $1; }
|
||||
| function_declaration_statement { $$ = $1; }
|
||||
| class_declaration_statement { $$ = $1; }
|
||||
statement
|
||||
| function_declaration_statement
|
||||
| class_declaration_statement
|
||||
| T_HALT_COMPILER
|
||||
{ throw new Error('__HALT_COMPILER() can only be used from the outermost scope', attributes()); }
|
||||
;
|
||||
|
||||
non_empty_statement:
|
||||
'{' inner_statement_list '}'
|
||||
{
|
||||
if ($2) {
|
||||
$$ = $2; prependLeadingComments($$);
|
||||
} else {
|
||||
makeNop($$, $this->startAttributeStack[#1], $this->endAttributes);
|
||||
if (null === $$) { $$ = array(); }
|
||||
}
|
||||
}
|
||||
| T_IF '(' expr ')' statement elseif_list else_single
|
||||
{ $$ = Stmt\If_[$3, ['stmts' => toArray($5), 'elseifs' => $6, 'else' => $7]]; }
|
||||
'{' inner_statement_list '}' { $$ = Stmt\Block[$2]; }
|
||||
| T_IF '(' expr ')' blocklike_statement elseif_list else_single
|
||||
{ $$ = Stmt\If_[$3, ['stmts' => $5, 'elseifs' => $6, 'else' => $7]]; }
|
||||
| T_IF '(' expr ')' ':' inner_statement_list new_elseif_list new_else_single T_ENDIF ';'
|
||||
{ $$ = Stmt\If_[$3, ['stmts' => $6, 'elseifs' => $7, 'else' => $8]]; }
|
||||
| T_WHILE '(' expr ')' while_statement { $$ = Stmt\While_[$3, $5]; }
|
||||
| T_DO statement T_WHILE '(' expr ')' ';' { $$ = Stmt\Do_ [$5, toArray($2)]; }
|
||||
| T_DO blocklike_statement T_WHILE '(' expr ')' ';' { $$ = Stmt\Do_ [$5, $2]; }
|
||||
| T_FOR '(' for_expr ';' for_expr ';' for_expr ')' for_statement
|
||||
{ $$ = Stmt\For_[['init' => $3, 'cond' => $5, 'loop' => $7, 'stmts' => $9]]; }
|
||||
| T_SWITCH '(' expr ')' switch_case_list { $$ = Stmt\Switch_[$3, $5]; }
|
||||
@ -387,17 +386,11 @@ non_empty_statement:
|
||||
| T_GLOBAL global_var_list semi { $$ = Stmt\Global_[$2]; }
|
||||
| T_STATIC static_var_list semi { $$ = Stmt\Static_[$2]; }
|
||||
| T_ECHO expr_list_forbid_comma semi { $$ = Stmt\Echo_[$2]; }
|
||||
| T_INLINE_HTML { $$ = Stmt\InlineHTML[$1]; }
|
||||
| expr semi {
|
||||
$e = $1;
|
||||
if ($e instanceof Expr\Throw_) {
|
||||
// For backwards-compatibility reasons, convert throw in statement position into
|
||||
// Stmt\Throw_ rather than Stmt\Expression(Expr\Throw_).
|
||||
$$ = Stmt\Throw_[$e->expr];
|
||||
} else {
|
||||
$$ = Stmt\Expression[$e];
|
||||
}
|
||||
| T_INLINE_HTML {
|
||||
$$ = Stmt\InlineHTML[$1];
|
||||
$$->setAttribute('hasLeadingNewline', $this->inlineHtmlHasLeadingNewline(#1));
|
||||
}
|
||||
| expr semi { $$ = Stmt\Expression[$1]; }
|
||||
| T_UNSET '(' variables_list ')' semi { $$ = Stmt\Unset_[$3]; }
|
||||
| T_FOREACH '(' expr T_AS foreach_variable ')' foreach_statement
|
||||
{ $$ = Stmt\Foreach_[$3, $5[0], ['keyVar' => null, 'byRef' => $5[1], 'stmts' => $7]]; }
|
||||
@ -410,14 +403,16 @@ non_empty_statement:
|
||||
{ $$ = Stmt\TryCatch[$3, $5, $6]; $this->checkTryCatch($$); }
|
||||
| T_GOTO identifier_not_reserved semi { $$ = Stmt\Goto_[$2]; }
|
||||
| identifier_not_reserved ':' { $$ = Stmt\Label[$1]; }
|
||||
| error { $$ = array(); /* means: no statement */ }
|
||||
| error { $$ = null; /* means: no statement */ }
|
||||
;
|
||||
|
||||
statement:
|
||||
non_empty_statement { $$ = $1; }
|
||||
| ';'
|
||||
{ makeNop($$, $this->startAttributeStack[#1], $this->endAttributes);
|
||||
if ($$ === null) $$ = array(); /* means: no statement */ }
|
||||
non_empty_statement
|
||||
| ';' { makeNop($$); }
|
||||
;
|
||||
|
||||
blocklike_statement:
|
||||
statement { toBlock($1); }
|
||||
;
|
||||
|
||||
catches:
|
||||
@ -441,7 +436,7 @@ optional_finally:
|
||||
;
|
||||
|
||||
variables_list:
|
||||
non_empty_variables_list optional_comma { $$ = $1; }
|
||||
non_empty_variables_list optional_comma
|
||||
;
|
||||
|
||||
non_empty_variables_list:
|
||||
@ -469,15 +464,16 @@ block_or_error:
|
||||
| error { $$ = []; }
|
||||
;
|
||||
|
||||
identifier_maybe_readonly:
|
||||
identifier_not_reserved { $$ = $1; }
|
||||
fn_identifier:
|
||||
identifier_not_reserved
|
||||
| T_READONLY { $$ = Node\Identifier[$1]; }
|
||||
| T_EXIT { $$ = Node\Identifier[$1]; }
|
||||
;
|
||||
|
||||
function_declaration_statement:
|
||||
T_FUNCTION optional_ref identifier_maybe_readonly '(' parameter_list ')' optional_return_type block_or_error
|
||||
T_FUNCTION optional_ref fn_identifier '(' parameter_list ')' optional_return_type block_or_error
|
||||
{ $$ = Stmt\Function_[$3, ['byRef' => $2, 'params' => $5, 'returnType' => $7, 'stmts' => $8, 'attrGroups' => []]]; }
|
||||
| attributes T_FUNCTION optional_ref identifier_maybe_readonly '(' parameter_list ')' optional_return_type block_or_error
|
||||
| attributes T_FUNCTION optional_ref fn_identifier '(' parameter_list ')' optional_return_type block_or_error
|
||||
{ $$ = Stmt\Function_[$4, ['byRef' => $3, 'params' => $6, 'returnType' => $8, 'stmts' => $9, 'attrGroups' => $1]]; }
|
||||
;
|
||||
|
||||
@ -509,11 +505,11 @@ enum_case_expr:
|
||||
|
||||
class_entry_type:
|
||||
T_CLASS { $$ = 0; }
|
||||
| class_modifiers T_CLASS { $$ = $1; }
|
||||
| class_modifiers T_CLASS
|
||||
;
|
||||
|
||||
class_modifiers:
|
||||
class_modifier { $$ = $1; }
|
||||
class_modifier
|
||||
| class_modifiers class_modifier { $this->checkClassModifier($1, $2, #2); $$ = $1 | $2; }
|
||||
;
|
||||
|
||||
@ -539,7 +535,7 @@ implements_list:
|
||||
;
|
||||
|
||||
class_name_list:
|
||||
non_empty_class_name_list no_comma { $$ = $1; }
|
||||
non_empty_class_name_list no_comma
|
||||
;
|
||||
|
||||
non_empty_class_name_list:
|
||||
@ -548,23 +544,23 @@ non_empty_class_name_list:
|
||||
;
|
||||
|
||||
for_statement:
|
||||
statement { $$ = toArray($1); }
|
||||
blocklike_statement
|
||||
| ':' inner_statement_list T_ENDFOR ';' { $$ = $2; }
|
||||
;
|
||||
|
||||
foreach_statement:
|
||||
statement { $$ = toArray($1); }
|
||||
blocklike_statement
|
||||
| ':' inner_statement_list T_ENDFOREACH ';' { $$ = $2; }
|
||||
;
|
||||
|
||||
declare_statement:
|
||||
non_empty_statement { $$ = toArray($1); }
|
||||
non_empty_statement { toBlock($1); }
|
||||
| ';' { $$ = null; }
|
||||
| ':' inner_statement_list T_ENDDECLARE ';' { $$ = $2; }
|
||||
;
|
||||
|
||||
declare_list:
|
||||
non_empty_declare_list no_comma { $$ = $1; }
|
||||
non_empty_declare_list no_comma
|
||||
;
|
||||
|
||||
non_empty_declare_list:
|
||||
@ -604,7 +600,7 @@ match:
|
||||
|
||||
match_arm_list:
|
||||
/* empty */ { $$ = []; }
|
||||
| non_empty_match_arm_list optional_comma { $$ = $1; }
|
||||
| non_empty_match_arm_list optional_comma
|
||||
;
|
||||
|
||||
non_empty_match_arm_list:
|
||||
@ -618,7 +614,7 @@ match_arm:
|
||||
;
|
||||
|
||||
while_statement:
|
||||
statement { $$ = toArray($1); }
|
||||
blocklike_statement { $$ = $1; }
|
||||
| ':' inner_statement_list T_ENDWHILE ';' { $$ = $2; }
|
||||
;
|
||||
|
||||
@ -628,7 +624,7 @@ elseif_list:
|
||||
;
|
||||
|
||||
elseif:
|
||||
T_ELSEIF '(' expr ')' statement { $$ = Stmt\ElseIf_[$3, toArray($5)]; }
|
||||
T_ELSEIF '(' expr ')' blocklike_statement { $$ = Stmt\ElseIf_[$3, $5]; }
|
||||
;
|
||||
|
||||
new_elseif_list:
|
||||
@ -643,7 +639,7 @@ new_elseif:
|
||||
|
||||
else_single:
|
||||
/* empty */ { $$ = null; }
|
||||
| T_ELSE statement { $$ = Stmt\Else_[toArray($2)]; }
|
||||
| T_ELSE blocklike_statement { $$ = Stmt\Else_[$2]; }
|
||||
;
|
||||
|
||||
new_else_single:
|
||||
@ -661,7 +657,7 @@ foreach_variable:
|
||||
;
|
||||
|
||||
parameter_list:
|
||||
non_empty_parameter_list optional_comma { $$ = $1; }
|
||||
non_empty_parameter_list optional_comma
|
||||
| /* empty */ { $$ = array(); }
|
||||
;
|
||||
|
||||
@ -680,32 +676,37 @@ property_modifier:
|
||||
T_PUBLIC { $$ = Modifiers::PUBLIC; }
|
||||
| T_PROTECTED { $$ = Modifiers::PROTECTED; }
|
||||
| T_PRIVATE { $$ = Modifiers::PRIVATE; }
|
||||
| T_PUBLIC_SET { $$ = Modifiers::PUBLIC_SET; }
|
||||
| T_PROTECTED_SET { $$ = Modifiers::PROTECTED_SET; }
|
||||
| T_PRIVATE_SET { $$ = Modifiers::PRIVATE_SET; }
|
||||
| T_READONLY { $$ = Modifiers::READONLY; }
|
||||
;
|
||||
|
||||
parameter:
|
||||
optional_attributes optional_property_modifiers optional_type_without_static
|
||||
optional_arg_ref optional_ellipsis plain_variable
|
||||
{ $$ = new Node\Param($6, null, $3, $4, $5, attributes(), $2, $1);
|
||||
$this->checkParam($$); }
|
||||
optional_arg_ref optional_ellipsis plain_variable optional_property_hook_list
|
||||
{ $$ = new Node\Param($6, null, $3, $4, $5, attributes(), $2, $1, $7);
|
||||
$this->checkParam($$);
|
||||
$this->addPropertyNameToHooks($$); }
|
||||
| optional_attributes optional_property_modifiers optional_type_without_static
|
||||
optional_arg_ref optional_ellipsis plain_variable '=' expr
|
||||
{ $$ = new Node\Param($6, $8, $3, $4, $5, attributes(), $2, $1);
|
||||
$this->checkParam($$); }
|
||||
optional_arg_ref optional_ellipsis plain_variable '=' expr optional_property_hook_list
|
||||
{ $$ = new Node\Param($6, $8, $3, $4, $5, attributes(), $2, $1, $9);
|
||||
$this->checkParam($$);
|
||||
$this->addPropertyNameToHooks($$); }
|
||||
| optional_attributes optional_property_modifiers optional_type_without_static
|
||||
optional_arg_ref optional_ellipsis error
|
||||
{ $$ = new Node\Param(Expr\Error[], null, $3, $4, $5, attributes(), $2, $1); }
|
||||
;
|
||||
|
||||
type_expr:
|
||||
type { $$ = $1; }
|
||||
type
|
||||
| '?' type { $$ = Node\NullableType[$2]; }
|
||||
| union_type { $$ = Node\UnionType[$1]; }
|
||||
| intersection_type { $$ = $1; }
|
||||
| intersection_type
|
||||
;
|
||||
|
||||
type:
|
||||
type_without_static { $$ = $1; }
|
||||
type_without_static
|
||||
| T_STATIC { $$ = Node\Name['static']; }
|
||||
;
|
||||
|
||||
@ -716,8 +717,8 @@ type_without_static:
|
||||
;
|
||||
|
||||
union_type_element:
|
||||
type { $$ = $1; }
|
||||
| '(' intersection_type ')' { $$ = $2; }
|
||||
type
|
||||
| '(' intersection_type ')' { $$ = $2; }
|
||||
;
|
||||
|
||||
union_type:
|
||||
@ -726,7 +727,7 @@ union_type:
|
||||
;
|
||||
|
||||
union_type_without_static_element:
|
||||
type_without_static { $$ = $1; }
|
||||
type_without_static
|
||||
| '(' intersection_type_without_static ')' { $$ = $2; }
|
||||
;
|
||||
|
||||
@ -757,15 +758,15 @@ intersection_type_without_static:
|
||||
;
|
||||
|
||||
type_expr_without_static:
|
||||
type_without_static { $$ = $1; }
|
||||
type_without_static
|
||||
| '?' type_without_static { $$ = Node\NullableType[$2]; }
|
||||
| union_type_without_static { $$ = Node\UnionType[$1]; }
|
||||
| intersection_type_without_static { $$ = $1; }
|
||||
| intersection_type_without_static
|
||||
;
|
||||
|
||||
optional_type_without_static:
|
||||
/* empty */ { $$ = null; }
|
||||
| type_expr_without_static { $$ = $1; }
|
||||
| type_expr_without_static
|
||||
;
|
||||
|
||||
optional_return_type:
|
||||
@ -798,7 +799,7 @@ argument:
|
||||
;
|
||||
|
||||
global_var_list:
|
||||
non_empty_global_var_list no_comma { $$ = $1; }
|
||||
non_empty_global_var_list no_comma
|
||||
;
|
||||
|
||||
non_empty_global_var_list:
|
||||
@ -807,11 +808,11 @@ non_empty_global_var_list:
|
||||
;
|
||||
|
||||
global_var:
|
||||
simple_variable { $$ = $1; }
|
||||
simple_variable
|
||||
;
|
||||
|
||||
static_var_list:
|
||||
non_empty_static_var_list no_comma { $$ = $1; }
|
||||
non_empty_static_var_list no_comma
|
||||
;
|
||||
|
||||
non_empty_static_var_list:
|
||||
@ -825,23 +826,32 @@ static_var:
|
||||
;
|
||||
|
||||
class_statement_list_ex:
|
||||
class_statement_list_ex class_statement { if ($2 !== null) { push($1, $2); } }
|
||||
class_statement_list_ex class_statement { if ($2 !== null) { push($1, $2); } else { $$ = $1; } }
|
||||
| /* empty */ { init(); }
|
||||
;
|
||||
|
||||
class_statement_list:
|
||||
class_statement_list_ex
|
||||
{ makeZeroLengthNop($nop, $this->lookaheadStartAttributes);
|
||||
{ makeZeroLengthNop($nop);
|
||||
if ($nop !== null) { $1[] = $nop; } $$ = $1; }
|
||||
;
|
||||
|
||||
class_statement:
|
||||
optional_attributes variable_modifiers optional_type_without_static property_declaration_list semi
|
||||
{ $$ = new Stmt\Property($2, $4, attributes(), $3, $1);
|
||||
$this->checkProperty($$, #2); }
|
||||
{ $$ = new Stmt\Property($2, $4, attributes(), $3, $1); }
|
||||
#if PHP8
|
||||
| optional_attributes variable_modifiers optional_type_without_static property_declaration_list '{' property_hook_list '}'
|
||||
{ $$ = new Stmt\Property($2, $4, attributes(), $3, $1, $6);
|
||||
$this->checkPropertyHooksForMultiProperty($$, #5);
|
||||
$this->checkEmptyPropertyHookList($6, #5);
|
||||
$this->addPropertyNameToHooks($$); }
|
||||
#endif
|
||||
| optional_attributes method_modifiers T_CONST class_const_list semi
|
||||
{ $$ = new Stmt\ClassConst($4, $2, attributes(), $1);
|
||||
$this->checkClassConst($$, #2); }
|
||||
| optional_attributes method_modifiers T_CONST type_expr class_const_list semi
|
||||
{ $$ = new Stmt\ClassConst($5, $2, attributes(), $1, $4);
|
||||
$this->checkClassConst($$, #2); }
|
||||
| optional_attributes method_modifiers T_FUNCTION optional_ref identifier_maybe_reserved '(' parameter_list ')'
|
||||
optional_return_type method_body
|
||||
{ $$ = Stmt\ClassMethod[$5, ['type' => $2, 'byRef' => $4, 'params' => $7, 'returnType' => $9, 'stmts' => $10, 'attrGroups' => $1]];
|
||||
@ -879,27 +889,27 @@ trait_method_reference_fully_qualified:
|
||||
name T_PAAMAYIM_NEKUDOTAYIM identifier_maybe_reserved { $$ = array($1, $3); }
|
||||
;
|
||||
trait_method_reference:
|
||||
trait_method_reference_fully_qualified { $$ = $1; }
|
||||
trait_method_reference_fully_qualified
|
||||
| identifier_maybe_reserved { $$ = array(null, $1); }
|
||||
;
|
||||
|
||||
method_body:
|
||||
';' /* abstract method */ { $$ = null; }
|
||||
| block_or_error { $$ = $1; }
|
||||
| block_or_error
|
||||
;
|
||||
|
||||
variable_modifiers:
|
||||
non_empty_member_modifiers { $$ = $1; }
|
||||
non_empty_member_modifiers
|
||||
| T_VAR { $$ = 0; }
|
||||
;
|
||||
|
||||
method_modifiers:
|
||||
/* empty */ { $$ = 0; }
|
||||
| non_empty_member_modifiers { $$ = $1; }
|
||||
| non_empty_member_modifiers
|
||||
;
|
||||
|
||||
non_empty_member_modifiers:
|
||||
member_modifier { $$ = $1; }
|
||||
member_modifier
|
||||
| non_empty_member_modifiers member_modifier { $this->checkModifier($1, $2, #2); $$ = $1 | $2; }
|
||||
;
|
||||
|
||||
@ -907,6 +917,9 @@ member_modifier:
|
||||
T_PUBLIC { $$ = Modifiers::PUBLIC; }
|
||||
| T_PROTECTED { $$ = Modifiers::PROTECTED; }
|
||||
| T_PRIVATE { $$ = Modifiers::PRIVATE; }
|
||||
| T_PUBLIC_SET { $$ = Modifiers::PUBLIC_SET; }
|
||||
| T_PROTECTED_SET { $$ = Modifiers::PROTECTED_SET; }
|
||||
| T_PRIVATE_SET { $$ = Modifiers::PRIVATE_SET; }
|
||||
| T_STATIC { $$ = Modifiers::STATIC; }
|
||||
| T_ABSTRACT { $$ = Modifiers::ABSTRACT; }
|
||||
| T_FINAL { $$ = Modifiers::FINAL; }
|
||||
@ -914,7 +927,7 @@ member_modifier:
|
||||
;
|
||||
|
||||
property_declaration_list:
|
||||
non_empty_property_declaration_list no_comma { $$ = $1; }
|
||||
non_empty_property_declaration_list no_comma
|
||||
;
|
||||
|
||||
non_empty_property_declaration_list:
|
||||
@ -932,12 +945,45 @@ property_declaration:
|
||||
| property_decl_name '=' expr { $$ = Node\PropertyItem[$1, $3]; }
|
||||
;
|
||||
|
||||
property_hook_list:
|
||||
/* empty */ { $$ = []; }
|
||||
| property_hook_list property_hook { push($1, $2); }
|
||||
;
|
||||
|
||||
optional_property_hook_list:
|
||||
/* empty */ { $$ = []; }
|
||||
#if PHP8
|
||||
| '{' property_hook_list '}' { $$ = $2; $this->checkEmptyPropertyHookList($2, #1); }
|
||||
#endif
|
||||
;
|
||||
|
||||
property_hook:
|
||||
optional_attributes property_hook_modifiers optional_ref identifier_not_reserved property_hook_body
|
||||
{ $$ = Node\PropertyHook[$4, $5, ['flags' => $2, 'byRef' => $3, 'params' => [], 'attrGroups' => $1]];
|
||||
$this->checkPropertyHook($$, null); }
|
||||
| optional_attributes property_hook_modifiers optional_ref identifier_not_reserved '(' parameter_list ')' property_hook_body
|
||||
{ $$ = Node\PropertyHook[$4, $8, ['flags' => $2, 'byRef' => $3, 'params' => $6, 'attrGroups' => $1]];
|
||||
$this->checkPropertyHook($$, #5); }
|
||||
;
|
||||
|
||||
property_hook_body:
|
||||
';' { $$ = null; }
|
||||
| '{' inner_statement_list '}' { $$ = $2; }
|
||||
| T_DOUBLE_ARROW expr ';' { $$ = $2; }
|
||||
;
|
||||
|
||||
property_hook_modifiers:
|
||||
/* empty */ { $$ = 0; }
|
||||
| property_hook_modifiers member_modifier
|
||||
{ $this->checkPropertyHookModifiers($1, $2, #2); $$ = $1 | $2; }
|
||||
;
|
||||
|
||||
expr_list_forbid_comma:
|
||||
non_empty_expr_list no_comma { $$ = $1; }
|
||||
non_empty_expr_list no_comma
|
||||
;
|
||||
|
||||
expr_list_allow_comma:
|
||||
non_empty_expr_list optional_comma { $$ = $1; }
|
||||
non_empty_expr_list optional_comma
|
||||
;
|
||||
|
||||
non_empty_expr_list:
|
||||
@ -947,11 +993,11 @@ non_empty_expr_list:
|
||||
|
||||
for_expr:
|
||||
/* empty */ { $$ = array(); }
|
||||
| expr_list_forbid_comma { $$ = $1; }
|
||||
| expr_list_forbid_comma
|
||||
;
|
||||
|
||||
expr:
|
||||
variable { $$ = $1; }
|
||||
variable
|
||||
| list_expr '=' expr { $$ = Expr\Assign[$1, $3]; }
|
||||
| array_short_syntax '=' expr
|
||||
{ $$ = Expr\Assign[$this->fixupArrayDestructuring($1), $3]; }
|
||||
@ -963,8 +1009,8 @@ expr:
|
||||
$this->emitError(new Error('Cannot assign new by reference', attributes()));
|
||||
}
|
||||
}
|
||||
| new_expr { $$ = $1; }
|
||||
| match { $$ = $1; }
|
||||
| new_expr
|
||||
| match
|
||||
| T_CLONE expr { $$ = Expr\Clone_[$2]; }
|
||||
| variable T_PLUS_EQUAL expr { $$ = Expr\AssignOp\Plus [$1, $3]; }
|
||||
| variable T_MINUS_EQUAL expr { $$ = Expr\AssignOp\Minus [$1, $3]; }
|
||||
@ -1036,12 +1082,10 @@ expr:
|
||||
| T_OBJECT_CAST expr { $$ = Expr\Cast\Object_ [$2]; }
|
||||
| T_BOOL_CAST expr { $$ = Expr\Cast\Bool_ [$2]; }
|
||||
| T_UNSET_CAST expr { $$ = Expr\Cast\Unset_ [$2]; }
|
||||
| T_EXIT exit_expr
|
||||
{ $attrs = attributes();
|
||||
$attrs['kind'] = strtolower($1) === 'exit' ? Expr\Exit_::KIND_EXIT : Expr\Exit_::KIND_DIE;
|
||||
$$ = new Expr\Exit_($2, $attrs); }
|
||||
| T_EXIT ctor_arguments
|
||||
{ $$ = $this->createExitExpr($1, #1, $2, attributes()); }
|
||||
| '@' expr { $$ = Expr\ErrorSuppress[$2]; }
|
||||
| scalar { $$ = $1; }
|
||||
| scalar
|
||||
| '`' backticks_expr '`' { $$ = Expr\ShellExec[$2]; }
|
||||
| T_PRINT expr { $$ = Expr\Print_[$2]; }
|
||||
| T_YIELD { $$ = Expr\Yield_[null, null]; }
|
||||
@ -1070,24 +1114,33 @@ expr:
|
||||
;
|
||||
|
||||
anonymous_class:
|
||||
optional_attributes T_CLASS ctor_arguments extends_from implements_list '{' class_statement_list '}'
|
||||
{ $$ = array(Stmt\Class_[null, ['type' => 0, 'extends' => $4, 'implements' => $5, 'stmts' => $7, 'attrGroups' => $1]], $3);
|
||||
optional_attributes class_entry_type ctor_arguments extends_from implements_list '{' class_statement_list '}'
|
||||
{ $$ = array(Stmt\Class_[null, ['type' => $2, 'extends' => $4, 'implements' => $5, 'stmts' => $7, 'attrGroups' => $1]], $3);
|
||||
$this->checkClass($$[0], -1); }
|
||||
;
|
||||
|
||||
new_expr:
|
||||
T_NEW class_name_reference ctor_arguments { $$ = Expr\New_[$2, $3]; }
|
||||
new_dereferenceable:
|
||||
T_NEW class_name_reference argument_list { $$ = Expr\New_[$2, $3]; }
|
||||
| T_NEW anonymous_class
|
||||
{ list($class, $ctorArgs) = $2; $$ = Expr\New_[$class, $ctorArgs]; }
|
||||
;
|
||||
|
||||
new_non_dereferenceable:
|
||||
T_NEW class_name_reference { $$ = Expr\New_[$2, []]; }
|
||||
;
|
||||
|
||||
new_expr:
|
||||
new_dereferenceable
|
||||
| new_non_dereferenceable
|
||||
;
|
||||
|
||||
lexical_vars:
|
||||
/* empty */ { $$ = array(); }
|
||||
| T_USE '(' lexical_var_list ')' { $$ = $3; }
|
||||
;
|
||||
|
||||
lexical_var_list:
|
||||
non_empty_lexical_var_list optional_comma { $$ = $1; }
|
||||
non_empty_lexical_var_list optional_comma
|
||||
;
|
||||
|
||||
non_empty_lexical_var_list:
|
||||
@ -1113,7 +1166,7 @@ function_call:
|
||||
|
||||
class_name:
|
||||
T_STATIC { $$ = Name[$1]; }
|
||||
| name { $$ = $1; }
|
||||
| name
|
||||
;
|
||||
|
||||
name:
|
||||
@ -1124,32 +1177,27 @@ name:
|
||||
;
|
||||
|
||||
class_name_reference:
|
||||
class_name { $$ = $1; }
|
||||
| new_variable { $$ = $1; }
|
||||
class_name
|
||||
| new_variable
|
||||
| '(' expr ')' { $$ = $2; }
|
||||
| error { $$ = Expr\Error[]; $this->errorState = 2; }
|
||||
;
|
||||
|
||||
class_name_or_var:
|
||||
class_name { $$ = $1; }
|
||||
| fully_dereferencable { $$ = $1; }
|
||||
;
|
||||
|
||||
exit_expr:
|
||||
/* empty */ { $$ = null; }
|
||||
| '(' optional_expr ')' { $$ = $2; }
|
||||
class_name
|
||||
| fully_dereferenceable
|
||||
;
|
||||
|
||||
backticks_expr:
|
||||
/* empty */ { $$ = array(); }
|
||||
| T_ENCAPSED_AND_WHITESPACE
|
||||
{ $$ = array(Node\InterpolatedStringPart[Scalar\String_::parseEscapeSequences($1, '`')]); }
|
||||
| encaps_list { parseEncapsed($1, '`', true); $$ = $1; }
|
||||
| encaps_string_part
|
||||
{ $$ = array($1); parseEncapsed($$, '`', $this->phpVersion->supportsUnicodeEscapes()); }
|
||||
| encaps_list { parseEncapsed($1, '`', $this->phpVersion->supportsUnicodeEscapes()); $$ = $1; }
|
||||
;
|
||||
|
||||
ctor_arguments:
|
||||
/* empty */ { $$ = array(); }
|
||||
| argument_list { $$ = $1; }
|
||||
| argument_list
|
||||
;
|
||||
|
||||
constant:
|
||||
@ -1162,6 +1210,7 @@ constant:
|
||||
| T_METHOD_C { $$ = Scalar\MagicConst\Method[]; }
|
||||
| T_FUNC_C { $$ = Scalar\MagicConst\Function_[]; }
|
||||
| T_NS_C { $$ = Scalar\MagicConst\Namespace_[]; }
|
||||
| T_PROPERTY_C { $$ = Scalar\MagicConst\Property[]; }
|
||||
;
|
||||
|
||||
class_constant:
|
||||
@ -1181,25 +1230,26 @@ array_short_syntax:
|
||||
$$ = new Expr\Array_($2, $attrs); }
|
||||
;
|
||||
|
||||
dereferencable_scalar:
|
||||
dereferenceable_scalar:
|
||||
T_ARRAY '(' array_pair_list ')'
|
||||
{ $attrs = attributes(); $attrs['kind'] = Expr\Array_::KIND_LONG;
|
||||
$$ = new Expr\Array_($3, $attrs);
|
||||
$this->createdArrays->attach($$); }
|
||||
| array_short_syntax { $$ = $1; $this->createdArrays->attach($$); }
|
||||
| T_CONSTANT_ENCAPSED_STRING { $$ = Scalar\String_::fromString($1, attributes()); }
|
||||
| T_CONSTANT_ENCAPSED_STRING
|
||||
{ $$ = Scalar\String_::fromString($1, attributes(), $this->phpVersion->supportsUnicodeEscapes()); }
|
||||
| '"' encaps_list '"'
|
||||
{ $attrs = attributes(); $attrs['kind'] = Scalar\String_::KIND_DOUBLE_QUOTED;
|
||||
parseEncapsed($2, '"', true); $$ = new Scalar\InterpolatedString($2, $attrs); }
|
||||
parseEncapsed($2, '"', $this->phpVersion->supportsUnicodeEscapes()); $$ = new Scalar\InterpolatedString($2, $attrs); }
|
||||
;
|
||||
|
||||
scalar:
|
||||
T_LNUMBER
|
||||
{ $$ = $this->parseLNumber($1, attributes(), $this->phpVersion->allowsInvalidOctals()); }
|
||||
| T_DNUMBER { $$ = Scalar\Float_::fromString($1, attributes()); }
|
||||
| dereferencable_scalar { $$ = $1; }
|
||||
| constant { $$ = $1; }
|
||||
| class_constant { $$ = $1; }
|
||||
| dereferenceable_scalar
|
||||
| constant
|
||||
| class_constant
|
||||
| T_START_HEREDOC T_ENCAPSED_AND_WHITESPACE T_END_HEREDOC
|
||||
{ $$ = $this->parseDocString($1, $2, $3, attributes(), stackAttributes(#3), true); }
|
||||
| T_START_HEREDOC T_END_HEREDOC
|
||||
@ -1210,54 +1260,58 @@ scalar:
|
||||
|
||||
optional_expr:
|
||||
/* empty */ { $$ = null; }
|
||||
| expr { $$ = $1; }
|
||||
| expr
|
||||
;
|
||||
|
||||
fully_dereferencable:
|
||||
variable { $$ = $1; }
|
||||
fully_dereferenceable:
|
||||
variable
|
||||
| '(' expr ')' { $$ = $2; }
|
||||
| dereferencable_scalar { $$ = $1; }
|
||||
| class_constant { $$ = $1; }
|
||||
| dereferenceable_scalar
|
||||
| class_constant
|
||||
| new_dereferenceable
|
||||
;
|
||||
|
||||
array_object_dereferencable:
|
||||
fully_dereferencable { $$ = $1; }
|
||||
| constant { $$ = $1; }
|
||||
array_object_dereferenceable:
|
||||
fully_dereferenceable
|
||||
| constant
|
||||
;
|
||||
|
||||
callable_expr:
|
||||
callable_variable { $$ = $1; }
|
||||
callable_variable
|
||||
| '(' expr ')' { $$ = $2; }
|
||||
| dereferencable_scalar { $$ = $1; }
|
||||
| dereferenceable_scalar
|
||||
| new_dereferenceable
|
||||
;
|
||||
|
||||
callable_variable:
|
||||
simple_variable { $$ = $1; }
|
||||
| array_object_dereferencable '[' optional_expr ']' { $$ = Expr\ArrayDimFetch[$1, $3]; }
|
||||
| array_object_dereferencable '{' expr '}' { $$ = Expr\ArrayDimFetch[$1, $3]; }
|
||||
| function_call { $$ = $1; }
|
||||
| array_object_dereferencable T_OBJECT_OPERATOR property_name argument_list
|
||||
simple_variable
|
||||
| array_object_dereferenceable '[' optional_expr ']' { $$ = Expr\ArrayDimFetch[$1, $3]; }
|
||||
#if PHP7
|
||||
| array_object_dereferenceable '{' expr '}' { $$ = Expr\ArrayDimFetch[$1, $3]; }
|
||||
#endif
|
||||
| function_call
|
||||
| array_object_dereferenceable T_OBJECT_OPERATOR property_name argument_list
|
||||
{ $$ = Expr\MethodCall[$1, $3, $4]; }
|
||||
| array_object_dereferencable T_NULLSAFE_OBJECT_OPERATOR property_name argument_list
|
||||
| array_object_dereferenceable T_NULLSAFE_OBJECT_OPERATOR property_name argument_list
|
||||
{ $$ = Expr\NullsafeMethodCall[$1, $3, $4]; }
|
||||
;
|
||||
|
||||
optional_plain_variable:
|
||||
/* empty */ { $$ = null; }
|
||||
| plain_variable { $$ = $1; }
|
||||
| plain_variable
|
||||
;
|
||||
|
||||
variable:
|
||||
callable_variable { $$ = $1; }
|
||||
| static_member { $$ = $1; }
|
||||
| array_object_dereferencable T_OBJECT_OPERATOR property_name
|
||||
callable_variable
|
||||
| static_member
|
||||
| array_object_dereferenceable T_OBJECT_OPERATOR property_name
|
||||
{ $$ = Expr\PropertyFetch[$1, $3]; }
|
||||
| array_object_dereferencable T_NULLSAFE_OBJECT_OPERATOR property_name
|
||||
| array_object_dereferenceable T_NULLSAFE_OBJECT_OPERATOR property_name
|
||||
{ $$ = Expr\NullsafePropertyFetch[$1, $3]; }
|
||||
;
|
||||
|
||||
simple_variable:
|
||||
plain_variable { $$ = $1; }
|
||||
plain_variable
|
||||
| '$' '{' expr '}' { $$ = Expr\Variable[$3]; }
|
||||
| '$' simple_variable { $$ = Expr\Variable[$2]; }
|
||||
| '$' error { $$ = Expr\Variable[Expr\Error[]]; $this->errorState = 2; }
|
||||
@ -1274,9 +1328,11 @@ static_member:
|
||||
;
|
||||
|
||||
new_variable:
|
||||
simple_variable { $$ = $1; }
|
||||
simple_variable
|
||||
| new_variable '[' optional_expr ']' { $$ = Expr\ArrayDimFetch[$1, $3]; }
|
||||
#if PHP7
|
||||
| new_variable '{' expr '}' { $$ = Expr\ArrayDimFetch[$1, $3]; }
|
||||
#endif
|
||||
| new_variable T_OBJECT_OPERATOR property_name { $$ = Expr\PropertyFetch[$1, $3]; }
|
||||
| new_variable T_NULLSAFE_OBJECT_OPERATOR property_name { $$ = Expr\NullsafePropertyFetch[$1, $3]; }
|
||||
| class_name T_PAAMAYIM_NEKUDOTAYIM static_member_prop_name
|
||||
@ -1286,15 +1342,15 @@ new_variable:
|
||||
;
|
||||
|
||||
member_name:
|
||||
identifier_maybe_reserved { $$ = $1; }
|
||||
identifier_maybe_reserved
|
||||
| '{' expr '}' { $$ = $2; }
|
||||
| simple_variable { $$ = $1; }
|
||||
| simple_variable
|
||||
;
|
||||
|
||||
property_name:
|
||||
identifier_not_reserved { $$ = $1; }
|
||||
identifier_not_reserved
|
||||
| '{' expr '}' { $$ = $2; }
|
||||
| simple_variable { $$ = $1; }
|
||||
| simple_variable
|
||||
| error { $$ = Expr\Error[]; $this->errorState = 2; }
|
||||
;
|
||||
|
||||
@ -1331,7 +1387,7 @@ array_pair:
|
||||
| /* empty */
|
||||
{ /* Create an Error node now to remember the position. We'll later either report an error,
|
||||
or convert this into a null element, depending on whether this is a creation or destructuring context. */
|
||||
$attrs = $this->createEmptyElemAttributes($this->lookaheadStartAttributes);
|
||||
$attrs = $this->createEmptyElemAttributes($this->tokenPos);
|
||||
$$ = new Node\ArrayItem(new Expr\Error($attrs), null, false, $attrs); }
|
||||
;
|
||||
|
||||
@ -1343,7 +1399,8 @@ encaps_list:
|
||||
;
|
||||
|
||||
encaps_string_part:
|
||||
T_ENCAPSED_AND_WHITESPACE { $$ = Node\InterpolatedStringPart[$1]; }
|
||||
T_ENCAPSED_AND_WHITESPACE
|
||||
{ $attrs = attributes(); $attrs['rawValue'] = $1; $$ = new Node\InterpolatedStringPart($1, $attrs); }
|
||||
;
|
||||
|
||||
encaps_str_varname:
|
||||
@ -1351,7 +1408,7 @@ encaps_str_varname:
|
||||
;
|
||||
|
||||
encaps_var:
|
||||
plain_variable { $$ = $1; }
|
||||
plain_variable
|
||||
| plain_variable '[' encaps_var_offset ']' { $$ = Expr\ArrayDimFetch[$1, $3]; }
|
||||
| plain_variable T_OBJECT_OPERATOR identifier_not_reserved
|
||||
{ $$ = Expr\PropertyFetch[$1, $3]; }
|
||||
@ -1368,7 +1425,7 @@ encaps_var_offset:
|
||||
T_STRING { $$ = Scalar\String_[$1]; }
|
||||
| T_NUM_STRING { $$ = $this->parseNumString($1, attributes()); }
|
||||
| '-' T_NUM_STRING { $$ = $this->parseNumString('-' . $2, attributes()); }
|
||||
| plain_variable { $$ = $1; }
|
||||
| plain_variable
|
||||
;
|
||||
|
||||
%%
|
||||
|
@ -23,6 +23,7 @@ function preprocessGrammar($code) {
|
||||
$code = resolveNodes($code);
|
||||
$code = resolveMacros($code);
|
||||
$code = resolveStackAccess($code);
|
||||
$code = str_replace('$this', '$self', $code);
|
||||
|
||||
return $code;
|
||||
}
|
||||
@ -65,13 +66,13 @@ function resolveMacros($code) {
|
||||
|
||||
if ('attributes' === $name) {
|
||||
assertArgs(0, $args, $name);
|
||||
return '$this->startAttributeStack[#1] + $this->endAttributes';
|
||||
return '$this->getAttributes($this->tokenStartStack[#1], $this->tokenEndStack[$stackPos])';
|
||||
}
|
||||
|
||||
if ('stackAttributes' === $name) {
|
||||
assertArgs(1, $args, $name);
|
||||
return '$this->startAttributeStack[' . $args[0] . ']'
|
||||
. ' + $this->endAttributeStack[' . $args[0] . ']';
|
||||
return '$this->getAttributes($this->tokenStartStack[' . $args[0] . '], '
|
||||
. ' $this->tokenEndStack[' . $args[0] . '])';
|
||||
}
|
||||
|
||||
if ('init' === $name) {
|
||||
@ -87,14 +88,15 @@ function resolveMacros($code) {
|
||||
if ('pushNormalizing' === $name) {
|
||||
assertArgs(2, $args, $name);
|
||||
|
||||
return 'if (is_array(' . $args[1] . ')) { $$ = array_merge(' . $args[0] . ', ' . $args[1] . '); }'
|
||||
. ' else { ' . $args[0] . '[] = ' . $args[1] . '; $$ = ' . $args[0] . '; }';
|
||||
return 'if (' . $args[1] . ' !== null) { ' . $args[0] . '[] = ' . $args[1] . '; } $$ = ' . $args[0] . ';';
|
||||
}
|
||||
|
||||
if ('toArray' == $name) {
|
||||
if ('toBlock' == $name) {
|
||||
assertArgs(1, $args, $name);
|
||||
|
||||
return 'is_array(' . $args[0] . ') ? ' . $args[0] . ' : array(' . $args[0] . ')';
|
||||
return 'if (' . $args[0] . ' instanceof Stmt\Block) { $$ = ' . $args[0] . '->stmts; } '
|
||||
. 'else if (' . $args[0] . ' === null) { $$ = []; } '
|
||||
. 'else { $$ = [' . $args[0] . ']; }';
|
||||
}
|
||||
|
||||
if ('parseVar' === $name) {
|
||||
@ -111,30 +113,15 @@ function resolveMacros($code) {
|
||||
}
|
||||
|
||||
if ('makeNop' === $name) {
|
||||
assertArgs(3, $args, $name);
|
||||
assertArgs(1, $args, $name);
|
||||
|
||||
return '$startAttributes = ' . $args[1] . ';'
|
||||
. ' if (isset($startAttributes[\'comments\']))'
|
||||
. ' { ' . $args[0] . ' = new Stmt\Nop($startAttributes + ' . $args[2] . '); }'
|
||||
. ' else { ' . $args[0] . ' = null; }';
|
||||
return $args[0] . ' = $this->maybeCreateNop($this->tokenStartStack[#1], $this->tokenEndStack[$stackPos])';
|
||||
}
|
||||
|
||||
if ('makeZeroLengthNop' == $name) {
|
||||
assertArgs(2, $args, $name);
|
||||
|
||||
return '$startAttributes = ' . $args[1] . ';'
|
||||
. ' if (isset($startAttributes[\'comments\']))'
|
||||
. ' { ' . $args[0] . ' = new Stmt\Nop($this->createCommentNopAttributes($startAttributes[\'comments\'])); }'
|
||||
. ' else { ' . $args[0] . ' = null; }';
|
||||
}
|
||||
|
||||
if ('prependLeadingComments' === $name) {
|
||||
assertArgs(1, $args, $name);
|
||||
|
||||
return '$attrs = $this->startAttributeStack[#1]; $stmts = ' . $args[0] . '; '
|
||||
. 'if (!empty($attrs[\'comments\'])) {'
|
||||
. '$stmts[0]->setAttribute(\'comments\', '
|
||||
. 'array_merge($attrs[\'comments\'], $stmts[0]->getAttribute(\'comments\', []))); }';
|
||||
return $args[0] . ' = $this->maybeCreateZeroLengthNop($this->tokenPos);';
|
||||
}
|
||||
|
||||
return $matches[0];
|
||||
|
@ -13,21 +13,22 @@ use PhpParser\Node\Identifier;
|
||||
use PhpParser\Node\Stmt;
|
||||
|
||||
class ClassConst implements PhpParser\Builder {
|
||||
/** @var int */
|
||||
protected $flags = 0;
|
||||
protected int $flags = 0;
|
||||
/** @var array<string, mixed> */
|
||||
protected $attributes = [];
|
||||
protected array $attributes = [];
|
||||
/** @var list<Const_> */
|
||||
protected $constants = [];
|
||||
protected array $constants = [];
|
||||
|
||||
/** @var list<Node\AttributeGroup> */
|
||||
protected $attributeGroups = [];
|
||||
protected array $attributeGroups = [];
|
||||
/** @var Identifier|Node\Name|Node\ComplexType|null */
|
||||
protected ?Node $type = null;
|
||||
|
||||
/**
|
||||
* Creates a class constant builder
|
||||
*
|
||||
* @param string|Identifier $name Name
|
||||
* @param Node\Expr|bool|null|int|float|string|array $value Value
|
||||
* @param string|Identifier $name Name
|
||||
* @param Node\Expr|bool|null|int|float|string|array|\UnitEnum $value Value
|
||||
*/
|
||||
public function __construct($name, $value) {
|
||||
$this->constants = [new Const_($name, BuilderHelpers::normalizeValue($value))];
|
||||
@ -36,8 +37,8 @@ class ClassConst implements PhpParser\Builder {
|
||||
/**
|
||||
* Add another constant to const group
|
||||
*
|
||||
* @param string|Identifier $name Name
|
||||
* @param Node\Expr|bool|null|int|float|string|array $value Value
|
||||
* @param string|Identifier $name Name
|
||||
* @param Node\Expr|bool|null|int|float|string|array|\UnitEnum $value Value
|
||||
*
|
||||
* @return $this The builder instance (for fluid interface)
|
||||
*/
|
||||
@ -119,6 +120,19 @@ class ClassConst implements PhpParser\Builder {
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the constant type.
|
||||
*
|
||||
* @param string|Node\Name|Identifier|Node\ComplexType $type
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setType($type) {
|
||||
$this->type = BuilderHelpers::normalizeType($type);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the built class node.
|
||||
*
|
||||
@ -129,7 +143,8 @@ class ClassConst implements PhpParser\Builder {
|
||||
$this->constants,
|
||||
$this->flags,
|
||||
$this->attributes,
|
||||
$this->attributeGroups
|
||||
$this->attributeGroups,
|
||||
$this->type
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -10,26 +10,21 @@ use PhpParser\Node\Name;
|
||||
use PhpParser\Node\Stmt;
|
||||
|
||||
class Class_ extends Declaration {
|
||||
/** @var string */
|
||||
protected $name;
|
||||
|
||||
/** @var Name|null */
|
||||
protected $extends = null;
|
||||
protected string $name;
|
||||
protected ?Name $extends = null;
|
||||
/** @var list<Name> */
|
||||
protected $implements = [];
|
||||
/** @var int */
|
||||
protected $flags = 0;
|
||||
|
||||
protected array $implements = [];
|
||||
protected int $flags = 0;
|
||||
/** @var list<Stmt\TraitUse> */
|
||||
protected $uses = [];
|
||||
protected array $uses = [];
|
||||
/** @var list<Stmt\ClassConst> */
|
||||
protected $constants = [];
|
||||
protected array $constants = [];
|
||||
/** @var list<Stmt\Property> */
|
||||
protected $properties = [];
|
||||
protected array $properties = [];
|
||||
/** @var list<Stmt\ClassMethod> */
|
||||
protected $methods = [];
|
||||
protected array $methods = [];
|
||||
/** @var list<Node\AttributeGroup> */
|
||||
protected $attributeGroups = [];
|
||||
protected array $attributeGroups = [];
|
||||
|
||||
/**
|
||||
* Creates a class builder.
|
||||
|
@ -7,7 +7,7 @@ use PhpParser\BuilderHelpers;
|
||||
|
||||
abstract class Declaration implements PhpParser\Builder {
|
||||
/** @var array<string, mixed> */
|
||||
protected $attributes = [];
|
||||
protected array $attributes = [];
|
||||
|
||||
/**
|
||||
* Adds a statement.
|
||||
|
@ -13,18 +13,17 @@ use PhpParser\Node\Stmt;
|
||||
class EnumCase implements PhpParser\Builder {
|
||||
/** @var Identifier|string */
|
||||
protected $name;
|
||||
/** @var ?Node\Expr */
|
||||
protected $value = null;
|
||||
protected ?Node\Expr $value = null;
|
||||
/** @var array<string, mixed> */
|
||||
protected $attributes = [];
|
||||
protected array $attributes = [];
|
||||
|
||||
/** @var list<Node\AttributeGroup> */
|
||||
protected $attributeGroups = [];
|
||||
protected array $attributeGroups = [];
|
||||
|
||||
/**
|
||||
* Creates an enum case builder.
|
||||
*
|
||||
* @param string|Identifier $name Name
|
||||
* @param string|Identifier $name Name
|
||||
*/
|
||||
public function __construct($name) {
|
||||
$this->name = $name;
|
||||
|
@ -10,22 +10,20 @@ use PhpParser\Node\Name;
|
||||
use PhpParser\Node\Stmt;
|
||||
|
||||
class Enum_ extends Declaration {
|
||||
/** @var string */
|
||||
protected $name;
|
||||
/** @var Identifier|null */
|
||||
protected $scalarType = null;
|
||||
protected string $name;
|
||||
protected ?Identifier $scalarType = null;
|
||||
/** @var list<Name> */
|
||||
protected $implements = [];
|
||||
protected array $implements = [];
|
||||
/** @var list<Stmt\TraitUse> */
|
||||
protected $uses = [];
|
||||
protected array $uses = [];
|
||||
/** @var list<Stmt\EnumCase> */
|
||||
protected $enumCases = [];
|
||||
protected array $enumCases = [];
|
||||
/** @var list<Stmt\ClassConst> */
|
||||
protected $constants = [];
|
||||
protected array $constants = [];
|
||||
/** @var list<Stmt\ClassMethod> */
|
||||
protected $methods = [];
|
||||
protected array $methods = [];
|
||||
/** @var list<Node\AttributeGroup> */
|
||||
protected $attributeGroups = [];
|
||||
protected array $attributeGroups = [];
|
||||
|
||||
/**
|
||||
* Creates an enum builder.
|
||||
|
@ -6,13 +6,12 @@ use PhpParser\BuilderHelpers;
|
||||
use PhpParser\Node;
|
||||
|
||||
abstract class FunctionLike extends Declaration {
|
||||
/** @var bool */
|
||||
protected $returnByRef = false;
|
||||
protected bool $returnByRef = false;
|
||||
/** @var Node\Param[] */
|
||||
protected $params = [];
|
||||
protected array $params = [];
|
||||
|
||||
/** @var Node\Identifier|Node\Name|Node\ComplexType|null */
|
||||
protected $returnType = null;
|
||||
protected ?Node $returnType = null;
|
||||
|
||||
/**
|
||||
* Make the function return by reference.
|
||||
|
@ -8,13 +8,12 @@ use PhpParser\Node;
|
||||
use PhpParser\Node\Stmt;
|
||||
|
||||
class Function_ extends FunctionLike {
|
||||
/** @var string */
|
||||
protected $name;
|
||||
protected string $name;
|
||||
/** @var list<Stmt> */
|
||||
protected $stmts = [];
|
||||
protected array $stmts = [];
|
||||
|
||||
/** @var list<Node\AttributeGroup> */
|
||||
protected $attributeGroups = [];
|
||||
protected array $attributeGroups = [];
|
||||
|
||||
/**
|
||||
* Creates a function builder.
|
||||
|
@ -9,16 +9,15 @@ use PhpParser\Node\Name;
|
||||
use PhpParser\Node\Stmt;
|
||||
|
||||
class Interface_ extends Declaration {
|
||||
/** @var string */
|
||||
protected $name;
|
||||
protected string $name;
|
||||
/** @var list<Name> */
|
||||
protected $extends = [];
|
||||
protected array $extends = [];
|
||||
/** @var list<Stmt\ClassConst> */
|
||||
protected $constants = [];
|
||||
protected array $constants = [];
|
||||
/** @var list<Stmt\ClassMethod> */
|
||||
protected $methods = [];
|
||||
protected array $methods = [];
|
||||
/** @var list<Node\AttributeGroup> */
|
||||
protected $attributeGroups = [];
|
||||
protected array $attributeGroups = [];
|
||||
|
||||
/**
|
||||
* Creates an interface builder.
|
||||
|
@ -9,16 +9,15 @@ use PhpParser\Node;
|
||||
use PhpParser\Node\Stmt;
|
||||
|
||||
class Method extends FunctionLike {
|
||||
/** @var string */
|
||||
protected $name;
|
||||
/** @var int */
|
||||
protected $flags = 0;
|
||||
protected string $name;
|
||||
|
||||
protected int $flags = 0;
|
||||
|
||||
/** @var list<Stmt>|null */
|
||||
protected $stmts = [];
|
||||
protected ?array $stmts = [];
|
||||
|
||||
/** @var list<Node\AttributeGroup> */
|
||||
protected $attributeGroups = [];
|
||||
protected array $attributeGroups = [];
|
||||
|
||||
/**
|
||||
* Creates a method builder.
|
||||
|
@ -8,10 +8,9 @@ use PhpParser\Node;
|
||||
use PhpParser\Node\Stmt;
|
||||
|
||||
class Namespace_ extends Declaration {
|
||||
/** @var Node\Name|null */
|
||||
private $name;
|
||||
private ?Node\Name $name;
|
||||
/** @var Stmt[] */
|
||||
private $stmts = [];
|
||||
private array $stmts = [];
|
||||
|
||||
/**
|
||||
* Creates a namespace builder.
|
||||
|
@ -4,21 +4,19 @@ namespace PhpParser\Builder;
|
||||
|
||||
use PhpParser;
|
||||
use PhpParser\BuilderHelpers;
|
||||
use PhpParser\Modifiers;
|
||||
use PhpParser\Node;
|
||||
|
||||
class Param implements PhpParser\Builder {
|
||||
/** @var string */
|
||||
protected $name;
|
||||
/** @var Node\Expr|null */
|
||||
protected $default = null;
|
||||
protected string $name;
|
||||
protected ?Node\Expr $default = null;
|
||||
/** @var Node\Identifier|Node\Name|Node\ComplexType|null */
|
||||
protected $type = null;
|
||||
/** @var bool */
|
||||
protected $byRef = false;
|
||||
/** @var bool */
|
||||
protected $variadic = false;
|
||||
protected ?Node $type = null;
|
||||
protected bool $byRef = false;
|
||||
protected int $flags = 0;
|
||||
protected bool $variadic = false;
|
||||
/** @var list<Node\AttributeGroup> */
|
||||
protected $attributeGroups = [];
|
||||
protected array $attributeGroups = [];
|
||||
|
||||
/**
|
||||
* Creates a parameter builder.
|
||||
@ -80,6 +78,72 @@ class Param implements PhpParser\Builder {
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes the (promoted) parameter public.
|
||||
*
|
||||
* @return $this The builder instance (for fluid interface)
|
||||
*/
|
||||
public function makePublic() {
|
||||
$this->flags = BuilderHelpers::addModifier($this->flags, Modifiers::PUBLIC);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes the (promoted) parameter protected.
|
||||
*
|
||||
* @return $this The builder instance (for fluid interface)
|
||||
*/
|
||||
public function makeProtected() {
|
||||
$this->flags = BuilderHelpers::addModifier($this->flags, Modifiers::PROTECTED);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes the (promoted) parameter private.
|
||||
*
|
||||
* @return $this The builder instance (for fluid interface)
|
||||
*/
|
||||
public function makePrivate() {
|
||||
$this->flags = BuilderHelpers::addModifier($this->flags, Modifiers::PRIVATE);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes the (promoted) parameter readonly.
|
||||
*
|
||||
* @return $this The builder instance (for fluid interface)
|
||||
*/
|
||||
public function makeReadonly() {
|
||||
$this->flags = BuilderHelpers::addModifier($this->flags, Modifiers::READONLY);
|
||||
|
||||
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.
|
||||
*
|
||||
@ -101,7 +165,7 @@ class Param implements PhpParser\Builder {
|
||||
public function getNode(): Node {
|
||||
return new Node\Param(
|
||||
new Node\Expr\Variable($this->name),
|
||||
$this->default, $this->type, $this->byRef, $this->variadic, [], 0, $this->attributeGroups
|
||||
$this->default, $this->type, $this->byRef, $this->variadic, [], $this->flags, $this->attributeGroups
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -12,18 +12,19 @@ use PhpParser\Node\Stmt;
|
||||
use PhpParser\Node\ComplexType;
|
||||
|
||||
class Property implements PhpParser\Builder {
|
||||
/** @var string */
|
||||
protected $name;
|
||||
/** @var int */
|
||||
protected $flags = 0;
|
||||
/** @var Node\Expr|null */
|
||||
protected $default = null;
|
||||
protected string $name;
|
||||
|
||||
protected int $flags = 0;
|
||||
|
||||
protected ?Node\Expr $default = null;
|
||||
/** @var array<string, mixed> */
|
||||
protected $attributes = [];
|
||||
protected array $attributes = [];
|
||||
/** @var null|Identifier|Name|ComplexType */
|
||||
protected $type;
|
||||
protected ?Node $type = null;
|
||||
/** @var list<Node\AttributeGroup> */
|
||||
protected $attributeGroups = [];
|
||||
protected array $attributeGroups = [];
|
||||
/** @var list<Node\PropertyHook> */
|
||||
protected array $hooks = [];
|
||||
|
||||
/**
|
||||
* Creates a property builder.
|
||||
@ -89,6 +90,50 @@ class Property implements PhpParser\Builder {
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes the property abstract. Requires at least one property hook to be specified as well.
|
||||
*
|
||||
* @return $this The builder instance (for fluid interface)
|
||||
*/
|
||||
public function makeAbstract() {
|
||||
$this->flags = BuilderHelpers::addModifier($this->flags, Modifiers::ABSTRACT);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes the property final.
|
||||
*
|
||||
* @return $this The builder instance (for fluid interface)
|
||||
*/
|
||||
public function makeFinal() {
|
||||
$this->flags = BuilderHelpers::addModifier($this->flags, Modifiers::FINAL);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gives the property private(set) visibility.
|
||||
*
|
||||
* @return $this The builder instance (for fluid interface)
|
||||
*/
|
||||
public function makePrivateSet() {
|
||||
$this->flags = BuilderHelpers::addModifier($this->flags, Modifiers::PRIVATE_SET);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gives the property protected(set) visibility.
|
||||
*
|
||||
* @return $this The builder instance (for fluid interface)
|
||||
*/
|
||||
public function makeProtectedSet() {
|
||||
$this->flags = BuilderHelpers::addModifier($this->flags, Modifiers::PROTECTED_SET);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets default value for the property.
|
||||
*
|
||||
@ -143,12 +188,27 @@ class Property implements PhpParser\Builder {
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a property hook.
|
||||
*
|
||||
* @return $this The builder instance (for fluid interface)
|
||||
*/
|
||||
public function addHook(Node\PropertyHook $hook) {
|
||||
$this->hooks[] = $hook;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the built class node.
|
||||
*
|
||||
* @return Stmt\Property The built property node
|
||||
*/
|
||||
public function getNode(): PhpParser\Node {
|
||||
if ($this->flags & Modifiers::ABSTRACT && !$this->hooks) {
|
||||
throw new PhpParser\Error('Only hooked properties may be declared abstract');
|
||||
}
|
||||
|
||||
return new Stmt\Property(
|
||||
$this->flags !== 0 ? $this->flags : Modifiers::PUBLIC,
|
||||
[
|
||||
@ -156,7 +216,8 @@ class Property implements PhpParser\Builder {
|
||||
],
|
||||
$this->attributes,
|
||||
$this->type,
|
||||
$this->attributeGroups
|
||||
$this->attributeGroups,
|
||||
$this->hooks
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -9,9 +9,9 @@ use PhpParser\Node\Stmt;
|
||||
|
||||
class TraitUse implements Builder {
|
||||
/** @var Node\Name[] */
|
||||
protected $traits = [];
|
||||
protected array $traits = [];
|
||||
/** @var Stmt\TraitUseAdaptation[] */
|
||||
protected $adaptations = [];
|
||||
protected array $adaptations = [];
|
||||
|
||||
/**
|
||||
* Creates a trait use builder.
|
||||
|
@ -13,23 +13,18 @@ class TraitUseAdaptation implements Builder {
|
||||
private const TYPE_ALIAS = 1;
|
||||
private const TYPE_PRECEDENCE = 2;
|
||||
|
||||
/** @var int Type of building adaptation */
|
||||
protected $type;
|
||||
/** @var Node\Name|null */
|
||||
protected $trait;
|
||||
/** @var Node\Identifier */
|
||||
protected $method;
|
||||
/** @var int|null */
|
||||
protected $modifier = null;
|
||||
/** @var Node\Identifier|null */
|
||||
protected $alias = null;
|
||||
protected int $type;
|
||||
protected ?Node\Name $trait;
|
||||
protected Node\Identifier $method;
|
||||
protected ?int $modifier = null;
|
||||
protected ?Node\Identifier $alias = null;
|
||||
/** @var Node\Name[] */
|
||||
protected $insteadof = [];
|
||||
protected array $insteadof = [];
|
||||
|
||||
/**
|
||||
* Creates a trait use adaptation builder.
|
||||
*
|
||||
* @param Node\Name|string|null $trait Name of adapted trait
|
||||
* @param Node\Name|string|null $trait Name of adapted trait
|
||||
* @param Node\Identifier|string $method Name of adapted method
|
||||
*/
|
||||
public function __construct($trait, $method) {
|
||||
|
@ -8,18 +8,17 @@ use PhpParser\Node;
|
||||
use PhpParser\Node\Stmt;
|
||||
|
||||
class Trait_ extends Declaration {
|
||||
/** @var string */
|
||||
protected $name;
|
||||
protected string $name;
|
||||
/** @var list<Stmt\TraitUse> */
|
||||
protected $uses = [];
|
||||
protected array $uses = [];
|
||||
/** @var list<Stmt\ClassConst> */
|
||||
protected $constants = [];
|
||||
protected array $constants = [];
|
||||
/** @var list<Stmt\Property> */
|
||||
protected $properties = [];
|
||||
protected array $properties = [];
|
||||
/** @var list<Stmt\ClassMethod> */
|
||||
protected $methods = [];
|
||||
protected array $methods = [];
|
||||
/** @var list<Node\AttributeGroup> */
|
||||
protected $attributeGroups = [];
|
||||
protected array $attributeGroups = [];
|
||||
|
||||
/**
|
||||
* Creates an interface builder.
|
||||
|
@ -8,18 +8,16 @@ use PhpParser\Node;
|
||||
use PhpParser\Node\Stmt;
|
||||
|
||||
class Use_ implements Builder {
|
||||
/** @var Node\Name */
|
||||
protected $name;
|
||||
/** @var int */
|
||||
protected $type;
|
||||
/** @var string|null */
|
||||
protected $alias = null;
|
||||
protected Node\Name $name;
|
||||
/** @var Stmt\Use_::TYPE_* */
|
||||
protected int $type;
|
||||
protected ?string $alias = null;
|
||||
|
||||
/**
|
||||
* Creates a name use (alias) builder.
|
||||
*
|
||||
* @param Node\Name|string $name Name of the entity (namespace, class, function, constant) to alias
|
||||
* @param int $type One of the Stmt\Use_::TYPE_* constants
|
||||
* @param Stmt\Use_::TYPE_* $type One of the Stmt\Use_::TYPE_* constants
|
||||
*/
|
||||
public function __construct($name, int $type) {
|
||||
$this->name = BuilderHelpers::normalizeName($name);
|
||||
|
@ -15,9 +15,7 @@ class BuilderFactory {
|
||||
* Creates an attribute node.
|
||||
*
|
||||
* @param string|Name $name Name of the attribute
|
||||
* @param array $args Attribute named arguments
|
||||
*
|
||||
* @return Node\Attribute
|
||||
* @param array $args Attribute named arguments
|
||||
*/
|
||||
public function attribute($name, array $args = []): Node\Attribute {
|
||||
return new Node\Attribute(
|
||||
@ -95,7 +93,7 @@ class BuilderFactory {
|
||||
/**
|
||||
* Creates a trait use adaptation builder.
|
||||
*
|
||||
* @param Node\Name|string|null $trait Trait name
|
||||
* @param Node\Name|string|null $trait Trait name
|
||||
* @param Node\Identifier|string $method Method name
|
||||
*
|
||||
* @return Builder\TraitUseAdaptation The created trait use adaptation builder
|
||||
@ -189,7 +187,7 @@ class BuilderFactory {
|
||||
/**
|
||||
* 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
|
||||
*
|
||||
* @return Builder\ClassConst The created use const builder
|
||||
@ -201,7 +199,7 @@ class BuilderFactory {
|
||||
/**
|
||||
* Creates an enum case builder.
|
||||
*
|
||||
* @param string|Identifier $name Name
|
||||
* @param string|Identifier $name Name
|
||||
*
|
||||
* @return Builder\EnumCase The created use const builder
|
||||
*/
|
||||
@ -212,9 +210,7 @@ class BuilderFactory {
|
||||
/**
|
||||
* Creates node a for a literal value.
|
||||
*
|
||||
* @param Expr|bool|null|int|float|string|array $value $value
|
||||
*
|
||||
* @return Expr
|
||||
* @param Expr|bool|null|int|float|string|array|\UnitEnum $value $value
|
||||
*/
|
||||
public function val($value): Expr {
|
||||
return BuilderHelpers::normalizeValue($value);
|
||||
@ -224,8 +220,6 @@ class BuilderFactory {
|
||||
* Creates variable node.
|
||||
*
|
||||
* @param string|Expr $name Name
|
||||
*
|
||||
* @return Expr\Variable
|
||||
*/
|
||||
public function var($name): Expr\Variable {
|
||||
if (!\is_string($name) && !$name instanceof Expr) {
|
||||
@ -262,9 +256,7 @@ class BuilderFactory {
|
||||
* Creates a function call node.
|
||||
*
|
||||
* @param string|Name|Expr $name Function name
|
||||
* @param array $args Function arguments
|
||||
*
|
||||
* @return Expr\FuncCall
|
||||
* @param array $args Function arguments
|
||||
*/
|
||||
public function funcCall($name, array $args = []): Expr\FuncCall {
|
||||
return new Expr\FuncCall(
|
||||
@ -276,11 +268,9 @@ class BuilderFactory {
|
||||
/**
|
||||
* Creates a method call node.
|
||||
*
|
||||
* @param Expr $var Variable the method is called on
|
||||
* @param Expr $var Variable the method is called on
|
||||
* @param string|Identifier|Expr $name Method name
|
||||
* @param array $args Method arguments
|
||||
*
|
||||
* @return Expr\MethodCall
|
||||
* @param array $args Method arguments
|
||||
*/
|
||||
public function methodCall(Expr $var, $name, array $args = []): Expr\MethodCall {
|
||||
return new Expr\MethodCall(
|
||||
@ -293,11 +283,9 @@ class BuilderFactory {
|
||||
/**
|
||||
* Creates a static method call node.
|
||||
*
|
||||
* @param string|Name|Expr $class Class name
|
||||
* @param string|Identifier|Expr $name Method name
|
||||
* @param array $args Method arguments
|
||||
*
|
||||
* @return Expr\StaticCall
|
||||
* @param string|Name|Expr $class Class name
|
||||
* @param string|Identifier|Expr $name Method name
|
||||
* @param array $args Method arguments
|
||||
*/
|
||||
public function staticCall($class, $name, array $args = []): Expr\StaticCall {
|
||||
return new Expr\StaticCall(
|
||||
@ -311,9 +299,7 @@ class BuilderFactory {
|
||||
* Creates an object creation node.
|
||||
*
|
||||
* @param string|Name|Expr $class Class name
|
||||
* @param array $args Constructor arguments
|
||||
*
|
||||
* @return Expr\New_
|
||||
* @param array $args Constructor arguments
|
||||
*/
|
||||
public function new($class, array $args = []): Expr\New_ {
|
||||
return new Expr\New_(
|
||||
@ -326,8 +312,6 @@ class BuilderFactory {
|
||||
* Creates a constant fetch node.
|
||||
*
|
||||
* @param string|Name $name Constant name
|
||||
*
|
||||
* @return Expr\ConstFetch
|
||||
*/
|
||||
public function constFetch($name): Expr\ConstFetch {
|
||||
return new Expr\ConstFetch(BuilderHelpers::normalizeName($name));
|
||||
@ -336,10 +320,8 @@ class BuilderFactory {
|
||||
/**
|
||||
* Creates a property fetch node.
|
||||
*
|
||||
* @param Expr $var Variable holding object
|
||||
* @param Expr $var Variable holding object
|
||||
* @param string|Identifier|Expr $name Property name
|
||||
*
|
||||
* @return Expr\PropertyFetch
|
||||
*/
|
||||
public function propertyFetch(Expr $var, $name): Expr\PropertyFetch {
|
||||
return new Expr\PropertyFetch($var, BuilderHelpers::normalizeIdentifierOrExpr($name));
|
||||
@ -349,9 +331,7 @@ class BuilderFactory {
|
||||
* Creates a class constant fetch node.
|
||||
*
|
||||
* @param string|Name|Expr $class Class name
|
||||
* @param string|Identifier|Expr $name Constant name
|
||||
*
|
||||
* @return Expr\ClassConstFetch
|
||||
* @param string|Identifier|Expr $name Constant name
|
||||
*/
|
||||
public function classConstFetch($class, $name): Expr\ClassConstFetch {
|
||||
return new Expr\ClassConstFetch(
|
||||
@ -364,8 +344,6 @@ class BuilderFactory {
|
||||
* Creates nested Concat nodes from a list of expressions.
|
||||
*
|
||||
* @param Expr|string ...$exprs Expressions or literal strings
|
||||
*
|
||||
* @return Concat
|
||||
*/
|
||||
public function concat(...$exprs): Concat {
|
||||
$numExprs = count($exprs);
|
||||
@ -382,7 +360,6 @@ class BuilderFactory {
|
||||
|
||||
/**
|
||||
* @param string|Expr $expr
|
||||
* @return Expr
|
||||
*/
|
||||
private function normalizeStringExpr($expr): Expr {
|
||||
if ($expr instanceof Expr) {
|
||||
|
@ -6,6 +6,7 @@ use PhpParser\Node\ComplexType;
|
||||
use PhpParser\Node\Expr;
|
||||
use PhpParser\Node\Identifier;
|
||||
use PhpParser\Node\Name;
|
||||
use PhpParser\Node\Name\FullyQualified;
|
||||
use PhpParser\Node\NullableType;
|
||||
use PhpParser\Node\Scalar;
|
||||
use PhpParser\Node\Stmt;
|
||||
@ -214,7 +215,7 @@ final class BuilderHelpers {
|
||||
* Normalizes a value: Converts nulls, booleans, integers,
|
||||
* floats, strings and arrays into their respective nodes
|
||||
*
|
||||
* @param Node\Expr|bool|null|int|float|string|array $value The value to normalize
|
||||
* @param Node\Expr|bool|null|int|float|string|array|\UnitEnum $value The value to normalize
|
||||
*
|
||||
* @return Expr The normalized value
|
||||
*/
|
||||
@ -268,6 +269,10 @@ final class BuilderHelpers {
|
||||
return new Expr\Array_($items);
|
||||
}
|
||||
|
||||
if ($value instanceof \UnitEnum) {
|
||||
return new Expr\ClassConstFetch(new FullyQualified(\get_class($value)), new Identifier($value->name));
|
||||
}
|
||||
|
||||
throw new \LogicException('Invalid value');
|
||||
}
|
||||
|
||||
@ -313,7 +318,7 @@ final class BuilderHelpers {
|
||||
* Adds a modifier and returns new modifier bitmask.
|
||||
*
|
||||
* @param int $modifiers Existing modifiers
|
||||
* @param int $modifier Modifier to set
|
||||
* @param int $modifier Modifier to set
|
||||
*
|
||||
* @return int New modifiers
|
||||
*/
|
||||
|
@ -3,28 +3,21 @@
|
||||
namespace PhpParser;
|
||||
|
||||
class Comment implements \JsonSerializable {
|
||||
/** @var string string */
|
||||
protected $text;
|
||||
/** @var int */
|
||||
protected $startLine;
|
||||
/** @var int */
|
||||
protected $startFilePos;
|
||||
/** @var int */
|
||||
protected $startTokenPos;
|
||||
/** @var int */
|
||||
protected $endLine;
|
||||
/** @var int */
|
||||
protected $endFilePos;
|
||||
/** @var int */
|
||||
protected $endTokenPos;
|
||||
protected string $text;
|
||||
protected int $startLine;
|
||||
protected int $startFilePos;
|
||||
protected int $startTokenPos;
|
||||
protected int $endLine;
|
||||
protected int $endFilePos;
|
||||
protected int $endTokenPos;
|
||||
|
||||
/**
|
||||
* Constructs a comment node.
|
||||
*
|
||||
* @param string $text Comment text (including comment delimiters like /*)
|
||||
* @param int $startLine Line number the comment started on
|
||||
* @param int $startFilePos File offset the comment started on
|
||||
* @param int $startTokenPos Token offset the comment started on
|
||||
* @param string $text Comment text (including comment delimiters like /*)
|
||||
* @param int $startLine Line number the comment started on
|
||||
* @param int $startFilePos File offset the comment started on
|
||||
* @param int $startTokenPos Token offset the comment started on
|
||||
*/
|
||||
public function __construct(
|
||||
string $text,
|
||||
@ -53,6 +46,7 @@ class Comment implements \JsonSerializable {
|
||||
* Gets the line number the comment started on.
|
||||
*
|
||||
* @return int Line number (or -1 if not available)
|
||||
* @phpstan-return -1|positive-int
|
||||
*/
|
||||
public function getStartLine(): int {
|
||||
return $this->startLine;
|
||||
@ -80,6 +74,7 @@ class Comment implements \JsonSerializable {
|
||||
* Gets the line number the comment ends on.
|
||||
*
|
||||
* @return int Line number (or -1 if not available)
|
||||
* @phpstan-return -1|positive-int
|
||||
*/
|
||||
public function getEndLine(): int {
|
||||
return $this->endLine;
|
||||
@ -103,39 +98,6 @@ class Comment implements \JsonSerializable {
|
||||
return $this->endTokenPos;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the line number the comment started on.
|
||||
*
|
||||
* @deprecated Use getStartLine() instead
|
||||
*
|
||||
* @return int Line number
|
||||
*/
|
||||
public function getLine(): int {
|
||||
return $this->startLine;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the file offset the comment started on.
|
||||
*
|
||||
* @deprecated Use getStartFilePos() instead
|
||||
*
|
||||
* @return int File offset
|
||||
*/
|
||||
public function getFilePos(): int {
|
||||
return $this->startFilePos;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the token offset the comment started on.
|
||||
*
|
||||
* @deprecated Use getStartTokenPos() instead
|
||||
*
|
||||
* @return int Token offset
|
||||
*/
|
||||
public function getTokenPos(): int {
|
||||
return $this->startTokenPos;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the comment text.
|
||||
*
|
||||
@ -150,19 +112,19 @@ class Comment implements \JsonSerializable {
|
||||
*
|
||||
* "Reformatted" here means that we try to clean up the whitespace at the
|
||||
* starts of the lines. This is necessary because we receive the comments
|
||||
* without trailing whitespace on the first line, but with trailing whitespace
|
||||
* without leading whitespace on the first line, but with leading whitespace
|
||||
* on all subsequent lines.
|
||||
*
|
||||
* @return mixed|string
|
||||
* Additionally, this normalizes CRLF newlines to LF newlines.
|
||||
*/
|
||||
public function getReformattedText() {
|
||||
$text = $this->text;
|
||||
public function getReformattedText(): string {
|
||||
$text = str_replace("\r\n", "\n", $this->text);
|
||||
$newlinePos = strpos($text, "\n");
|
||||
if (false === $newlinePos) {
|
||||
// Single line comments don't need further processing
|
||||
return $text;
|
||||
}
|
||||
if (preg_match('((*BSR_ANYCRLF)(*ANYCRLF)^.*(?:\R\s+\*.*)+$)', $text)) {
|
||||
if (preg_match('(^.*(?:\n\s+\*.*)+$)', $text)) {
|
||||
// Multi line comment of the type
|
||||
//
|
||||
// /*
|
||||
@ -171,9 +133,9 @@ class Comment implements \JsonSerializable {
|
||||
// */
|
||||
//
|
||||
// is handled by replacing the whitespace sequences before the * by a single space
|
||||
return preg_replace('(^\s+\*)m', ' *', $this->text);
|
||||
return preg_replace('(^\s+\*)m', ' *', $text);
|
||||
}
|
||||
if (preg_match('(^/\*\*?\s*[\r\n])', $text) && preg_match('(\n(\s*)\*/$)', $text, $matches)) {
|
||||
if (preg_match('(^/\*\*?\s*\n)', $text) && preg_match('(\n(\s*)\*/$)', $text, $matches)) {
|
||||
// Multi line comment of the type
|
||||
//
|
||||
// /*
|
||||
@ -227,8 +189,7 @@ class Comment implements \JsonSerializable {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
* @psalm-return array{nodeType:string, text:mixed, line:mixed, filePos:mixed}
|
||||
* @return array{nodeType:string, text:mixed, line:mixed, filePos:mixed}
|
||||
*/
|
||||
public function jsonSerialize(): array {
|
||||
// Technically not a node, but we make it look like one anyway
|
||||
|
@ -3,10 +3,9 @@
|
||||
namespace PhpParser;
|
||||
|
||||
class Error extends \RuntimeException {
|
||||
/** @var string */
|
||||
protected $rawMessage;
|
||||
protected string $rawMessage;
|
||||
/** @var array<string, mixed> */
|
||||
protected $attributes;
|
||||
protected array $attributes;
|
||||
|
||||
/**
|
||||
* Creates an Exception signifying a parse error.
|
||||
@ -33,6 +32,7 @@ class Error extends \RuntimeException {
|
||||
* Gets the line the error starts in.
|
||||
*
|
||||
* @return int Error start line
|
||||
* @phpstan-return -1|positive-int
|
||||
*/
|
||||
public function getStartLine(): int {
|
||||
return $this->attributes['startLine'] ?? -1;
|
||||
@ -42,6 +42,7 @@ class Error extends \RuntimeException {
|
||||
* Gets the line the error ends in.
|
||||
*
|
||||
* @return int Error end line
|
||||
* @phpstan-return -1|positive-int
|
||||
*/
|
||||
public function getEndLine(): int {
|
||||
return $this->attributes['endLine'] ?? -1;
|
||||
@ -90,8 +91,6 @@ class Error extends \RuntimeException {
|
||||
* Returns whether the error has start and end column information.
|
||||
*
|
||||
* For column information enable the startFilePos and endFilePos in the lexer options.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasColumnInfo(): bool {
|
||||
return isset($this->attributes['startFilePos'], $this->attributes['endFilePos']);
|
||||
@ -101,7 +100,6 @@ class Error extends \RuntimeException {
|
||||
* Gets the start column (1-based) into the line where the error started.
|
||||
*
|
||||
* @param string $code Source code of the file
|
||||
* @return int
|
||||
*/
|
||||
public function getStartColumn(string $code): int {
|
||||
if (!$this->hasColumnInfo()) {
|
||||
@ -115,7 +113,6 @@ class Error extends \RuntimeException {
|
||||
* Gets the end column (1-based) into the line where the error ended.
|
||||
*
|
||||
* @param string $code Source code of the file
|
||||
* @return int
|
||||
*/
|
||||
public function getEndColumn(string $code): int {
|
||||
if (!$this->hasColumnInfo()) {
|
||||
@ -144,7 +141,7 @@ class Error extends \RuntimeException {
|
||||
* Converts a file offset into a column.
|
||||
*
|
||||
* @param string $code Source code that $pos indexes into
|
||||
* @param int $pos 0-based position in $code
|
||||
* @param int $pos 0-based position in $code
|
||||
*
|
||||
* @return int 1-based column (relative to start of line)
|
||||
*/
|
||||
|
@ -12,7 +12,7 @@ use PhpParser\ErrorHandler;
|
||||
*/
|
||||
class Collecting implements ErrorHandler {
|
||||
/** @var Error[] Collected errors */
|
||||
private $errors = [];
|
||||
private array $errors = [];
|
||||
|
||||
public function handleError(Error $error): void {
|
||||
$this->errors[] = $error;
|
||||
@ -29,8 +29,6 @@ class Collecting implements ErrorHandler {
|
||||
|
||||
/**
|
||||
* Check whether there are any errors.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasErrors(): bool {
|
||||
return !empty($this->errors);
|
||||
|
@ -12,7 +12,7 @@ class DiffElem {
|
||||
public const TYPE_REPLACE = 3;
|
||||
|
||||
/** @var int One of the TYPE_* constants */
|
||||
public $type;
|
||||
public int $type;
|
||||
/** @var mixed Is null for add operations */
|
||||
public $old;
|
||||
/** @var mixed Is null for remove operations */
|
||||
|
@ -33,6 +33,8 @@ class Differ {
|
||||
* @return DiffElem[] Diff (edit script)
|
||||
*/
|
||||
public function diff(array $old, array $new): array {
|
||||
$old = \array_values($old);
|
||||
$new = \array_values($new);
|
||||
list($trace, $x, $y) = $this->calculateTrace($old, $new);
|
||||
return $this->extractDiff($trace, $x, $y, $old, $new);
|
||||
}
|
||||
@ -66,10 +68,10 @@ class Differ {
|
||||
for ($d = 0; $d <= $max; $d++) {
|
||||
$trace[] = $v;
|
||||
for ($k = -$d; $k <= $d; $k += 2) {
|
||||
if ($k === -$d || ($k !== $d && $v[$k-1] < $v[$k+1])) {
|
||||
$x = $v[$k+1];
|
||||
if ($k === -$d || ($k !== $d && $v[$k - 1] < $v[$k + 1])) {
|
||||
$x = $v[$k + 1];
|
||||
} else {
|
||||
$x = $v[$k-1] + 1;
|
||||
$x = $v[$k - 1] + 1;
|
||||
}
|
||||
|
||||
$y = $x - $k;
|
||||
@ -89,8 +91,6 @@ class Differ {
|
||||
|
||||
/**
|
||||
* @param array<int, array<int, int>> $trace
|
||||
* @param int $x
|
||||
* @param int $y
|
||||
* @param T[] $old
|
||||
* @param T[] $new
|
||||
* @return DiffElem[]
|
||||
@ -101,7 +101,7 @@ class Differ {
|
||||
$v = $trace[$d];
|
||||
$k = $x - $y;
|
||||
|
||||
if ($k === -$d || ($k !== $d && $v[$k-1] < $v[$k+1])) {
|
||||
if ($k === -$d || ($k !== $d && $v[$k - 1] < $v[$k + 1])) {
|
||||
$prevK = $k + 1;
|
||||
} else {
|
||||
$prevK = $k - 1;
|
||||
@ -111,7 +111,7 @@ class Differ {
|
||||
$prevY = $prevX - $prevK;
|
||||
|
||||
while ($x > $prevX && $y > $prevY) {
|
||||
$result[] = new DiffElem(DiffElem::TYPE_KEEP, $old[$x-1], $new[$y-1]);
|
||||
$result[] = new DiffElem(DiffElem::TYPE_KEEP, $old[$x - 1], $new[$y - 1]);
|
||||
$x--;
|
||||
$y--;
|
||||
}
|
||||
@ -121,12 +121,12 @@ class Differ {
|
||||
}
|
||||
|
||||
while ($x > $prevX) {
|
||||
$result[] = new DiffElem(DiffElem::TYPE_REMOVE, $old[$x-1], null);
|
||||
$result[] = new DiffElem(DiffElem::TYPE_REMOVE, $old[$x - 1], null);
|
||||
$x--;
|
||||
}
|
||||
|
||||
while ($y > $prevY) {
|
||||
$result[] = new DiffElem(DiffElem::TYPE_ADD, null, $new[$y-1]);
|
||||
$result[] = new DiffElem(DiffElem::TYPE_ADD, null, $new[$y - 1]);
|
||||
$y--;
|
||||
}
|
||||
}
|
||||
|
@ -17,15 +17,17 @@ use PhpParser\Node\Expr;
|
||||
*/
|
||||
class PrintableNewAnonClassNode extends Expr {
|
||||
/** @var Node\AttributeGroup[] PHP attribute groups */
|
||||
public $attrGroups;
|
||||
public array $attrGroups;
|
||||
/** @var int Modifiers */
|
||||
public int $flags;
|
||||
/** @var (Node\Arg|Node\VariadicPlaceholder)[] Arguments */
|
||||
public $args;
|
||||
public array $args;
|
||||
/** @var null|Node\Name Name of extended class */
|
||||
public $extends;
|
||||
public ?Node\Name $extends;
|
||||
/** @var Node\Name[] Names of implemented interfaces */
|
||||
public $implements;
|
||||
public array $implements;
|
||||
/** @var Node\Stmt[] Statements */
|
||||
public $stmts;
|
||||
public array $stmts;
|
||||
|
||||
/**
|
||||
* @param Node\AttributeGroup[] $attrGroups PHP attribute groups
|
||||
@ -36,11 +38,12 @@ class PrintableNewAnonClassNode extends Expr {
|
||||
* @param array<string, mixed> $attributes Attributes
|
||||
*/
|
||||
public function __construct(
|
||||
array $attrGroups, array $args, ?Node\Name $extends, array $implements,
|
||||
array $attrGroups, int $flags, array $args, ?Node\Name $extends, array $implements,
|
||||
array $stmts, array $attributes
|
||||
) {
|
||||
parent::__construct($attributes);
|
||||
$this->attrGroups = $attrGroups;
|
||||
$this->flags = $flags;
|
||||
$this->args = $args;
|
||||
$this->extends = $extends;
|
||||
$this->implements = $implements;
|
||||
@ -53,7 +56,7 @@ class PrintableNewAnonClassNode extends Expr {
|
||||
// We don't assert that $class->name is null here, to allow consumers to assign unique names
|
||||
// to anonymous classes for their own purposes. We simplify ignore the name here.
|
||||
return new self(
|
||||
$class->attrGroups, $newNode->args, $class->extends, $class->implements,
|
||||
$class->attrGroups, $class->flags, $newNode->args, $class->extends, $class->implements,
|
||||
$class->stmts, $newNode->getAttributes()
|
||||
);
|
||||
}
|
||||
@ -63,6 +66,6 @@ class PrintableNewAnonClassNode extends Expr {
|
||||
}
|
||||
|
||||
public function getSubNodeNames(): array {
|
||||
return ['attrGroups', 'args', 'extends', 'implements', 'stmts'];
|
||||
return ['attrGroups', 'flags', 'args', 'extends', 'implements', 'stmts'];
|
||||
}
|
||||
}
|
||||
|
@ -17,13 +17,13 @@ if (\PHP_VERSION_ID >= 80000) {
|
||||
*/
|
||||
class TokenPolyfill {
|
||||
/** @var int The ID of the token. Either a T_* constant of a character code < 256. */
|
||||
public $id;
|
||||
public int $id;
|
||||
/** @var string The textual content of the token. */
|
||||
public $text;
|
||||
public string $text;
|
||||
/** @var int The 1-based starting line of the token (or -1 if unknown). */
|
||||
public $line;
|
||||
public int $line;
|
||||
/** @var int The 0-based starting position of the token (or -1 if unknown). */
|
||||
public $pos;
|
||||
public int $pos;
|
||||
|
||||
/** @var array<int, bool> Tokens ignored by the PHP parser. */
|
||||
private const IGNORABLE_TOKENS = [
|
||||
@ -33,8 +33,8 @@ class TokenPolyfill {
|
||||
\T_OPEN_TAG => true,
|
||||
];
|
||||
|
||||
/** @var array<int, bool>|null Tokens that may be part of a T_NAME_* identifier. */
|
||||
private static $identifierTokens;
|
||||
/** @var array<int, bool> Tokens that may be part of a T_NAME_* identifier. */
|
||||
private static array $identifierTokens;
|
||||
|
||||
/**
|
||||
* Create a Token with the given ID and text, as well optional line and position information.
|
||||
@ -128,9 +128,6 @@ class TokenPolyfill {
|
||||
$line = 1;
|
||||
$pos = 0;
|
||||
$origTokens = \token_get_all($code, $flags);
|
||||
if (\PHP_VERSION_ID < 70400) {
|
||||
$origTokens = self::fixupBadCharacters($code, $origTokens);
|
||||
}
|
||||
|
||||
$numTokens = \count($origTokens);
|
||||
for ($i = 0; $i < $numTokens; $i++) {
|
||||
@ -217,38 +214,6 @@ class TokenPolyfill {
|
||||
return $tokens;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prior to PHP 7.4, token_get_all() simply dropped invalid characters from the token stream.
|
||||
* Detect such cases and replace them with T_BAD_CHARACTER.
|
||||
*/
|
||||
private static function fixupBadCharacters(string $code, array $origTokens): array {
|
||||
$newTokens = [];
|
||||
$pos = 0;
|
||||
foreach ($origTokens as $token) {
|
||||
$text = \is_string($token) ? $token : $token[1];
|
||||
$len = \strlen($text);
|
||||
if (substr($code, $pos, $len) !== $text) {
|
||||
$nextPos = strpos($code, $text, $pos);
|
||||
for ($i = $pos; $i < $nextPos; $i++) {
|
||||
// Don't bother including the line, we're not going to use it anyway.
|
||||
$newTokens[] = [\T_BAD_CHARACTER, $code[$i]];
|
||||
}
|
||||
$pos = $nextPos;
|
||||
}
|
||||
$pos += $len;
|
||||
$newTokens[] = $token;
|
||||
}
|
||||
|
||||
// Handle trailing invalid characters.
|
||||
$codeLen = \strlen($code);
|
||||
if ($pos !== $codeLen) {
|
||||
for ($i = $pos; $i < $codeLen; $i++) {
|
||||
$newTokens[] = [\T_BAD_CHARACTER, $code[$i]];
|
||||
}
|
||||
}
|
||||
return $newTokens;
|
||||
}
|
||||
|
||||
/** Initialize private static state needed by tokenize(). */
|
||||
private static function init(): void {
|
||||
if (isset(self::$identifierTokens)) {
|
||||
|
@ -11,27 +11,25 @@ use PhpParser\Token;
|
||||
*/
|
||||
class TokenStream {
|
||||
/** @var Token[] Tokens (in PhpToken::tokenize() format) */
|
||||
private $tokens;
|
||||
private array $tokens;
|
||||
/** @var int[] Map from position to indentation */
|
||||
private $indentMap;
|
||||
private array $indentMap;
|
||||
|
||||
/**
|
||||
* Create token stream instance.
|
||||
*
|
||||
* @param Token[] $tokens Tokens in PhpToken::tokenize() format
|
||||
*/
|
||||
public function __construct(array $tokens) {
|
||||
public function __construct(array $tokens, int $tabWidth) {
|
||||
$this->tokens = $tokens;
|
||||
$this->indentMap = $this->calcIndentMap();
|
||||
$this->indentMap = $this->calcIndentMap($tabWidth);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the given position is immediately surrounded by parenthesis.
|
||||
*
|
||||
* @param int $startPos Start position
|
||||
* @param int $endPos End position
|
||||
*
|
||||
* @return bool
|
||||
* @param int $endPos End position
|
||||
*/
|
||||
public function haveParens(int $startPos, int $endPos): bool {
|
||||
return $this->haveTokenImmediatelyBefore($startPos, '(')
|
||||
@ -42,9 +40,7 @@ class TokenStream {
|
||||
* Whether the given position is immediately surrounded by braces.
|
||||
*
|
||||
* @param int $startPos Start position
|
||||
* @param int $endPos End position
|
||||
*
|
||||
* @return bool
|
||||
* @param int $endPos End position
|
||||
*/
|
||||
public function haveBraces(int $startPos, int $endPos): bool {
|
||||
return ($this->haveTokenImmediatelyBefore($startPos, '{')
|
||||
@ -57,7 +53,7 @@ class TokenStream {
|
||||
*
|
||||
* During this check whitespace and comments are skipped.
|
||||
*
|
||||
* @param int $pos Position before which the token should occur
|
||||
* @param int $pos Position before which the token should occur
|
||||
* @param int|string $expectedTokenType Token to check for
|
||||
*
|
||||
* @return bool Whether the expected token was found
|
||||
@ -82,7 +78,7 @@ class TokenStream {
|
||||
*
|
||||
* During this check whitespace and comments are skipped.
|
||||
*
|
||||
* @param int $pos Position after which the token should occur
|
||||
* @param int $pos Position after which the token should occur
|
||||
* @param int|string $expectedTokenType Token to check for
|
||||
*
|
||||
* @return bool Whether the expected token was found
|
||||
@ -199,12 +195,6 @@ class TokenStream {
|
||||
return false;
|
||||
}
|
||||
|
||||
public function haveBracesInRange(int $startPos, int $endPos): bool {
|
||||
return $this->haveTokenInRange($startPos, $endPos, '{')
|
||||
|| $this->haveTokenInRange($startPos, $endPos, T_CURLY_OPEN)
|
||||
|| $this->haveTokenInRange($startPos, $endPos, '}');
|
||||
}
|
||||
|
||||
public function haveTagInRange(int $startPos, int $endPos): bool {
|
||||
return $this->haveTokenInRange($startPos, $endPos, \T_OPEN_TAG)
|
||||
|| $this->haveTokenInRange($startPos, $endPos, \T_CLOSE_TAG);
|
||||
@ -224,8 +214,8 @@ class TokenStream {
|
||||
/**
|
||||
* Get the code corresponding to a token offset range, optionally adjusted for indentation.
|
||||
*
|
||||
* @param int $from Token start position (inclusive)
|
||||
* @param int $to Token end position (exclusive)
|
||||
* @param int $from Token start position (inclusive)
|
||||
* @param int $to Token end position (exclusive)
|
||||
* @param int $indent By how much the code should be indented (can be negative as well)
|
||||
*
|
||||
* @return string Code corresponding to token range, adjusted for indentation
|
||||
@ -258,17 +248,21 @@ class TokenStream {
|
||||
*
|
||||
* @return int[] Token position to indentation map
|
||||
*/
|
||||
private function calcIndentMap(): array {
|
||||
private function calcIndentMap(int $tabWidth): array {
|
||||
$indentMap = [];
|
||||
$indent = 0;
|
||||
foreach ($this->tokens as $token) {
|
||||
foreach ($this->tokens as $i => $token) {
|
||||
$indentMap[] = $indent;
|
||||
|
||||
if ($token->id === \T_WHITESPACE) {
|
||||
$content = $token->text;
|
||||
$newlinePos = \strrpos($content, "\n");
|
||||
if (false !== $newlinePos) {
|
||||
$indent = \strlen($content) - $newlinePos - 1;
|
||||
$indent = $this->getIndent(\substr($content, $newlinePos + 1), $tabWidth);
|
||||
} elseif ($i === 1 && $this->tokens[0]->id === \T_OPEN_TAG &&
|
||||
$this->tokens[0]->text[\strlen($this->tokens[0]->text) - 1] === "\n") {
|
||||
// Special case: Newline at the end of opening tag followed by whitespace.
|
||||
$indent = $this->getIndent($content, $tabWidth);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -278,4 +272,11 @@ class TokenStream {
|
||||
|
||||
return $indentMap;
|
||||
}
|
||||
|
||||
private function getIndent(string $ws, int $tabWidth): int {
|
||||
$spaces = \substr_count($ws, " ");
|
||||
$tabs = \substr_count($ws, "\t");
|
||||
assert(\strlen($ws) === $spaces + $tabs);
|
||||
return $spaces + $tabs * $tabWidth;
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,7 @@ namespace PhpParser;
|
||||
|
||||
class JsonDecoder {
|
||||
/** @var \ReflectionClass<Node>[] Node type to reflection class map */
|
||||
private $reflectionClassCache;
|
||||
private array $reflectionClassCache;
|
||||
|
||||
/** @return mixed */
|
||||
public function decode(string $json) {
|
||||
|
@ -5,91 +5,37 @@ namespace PhpParser;
|
||||
require __DIR__ . '/compatibility_tokens.php';
|
||||
|
||||
class Lexer {
|
||||
/** @var string Code being tokenized */
|
||||
protected $code;
|
||||
/** @var list<Token> List of tokens */
|
||||
protected $tokens;
|
||||
/** @var int Current position in the token array */
|
||||
protected $pos;
|
||||
/** @var bool Whether the preceding closing PHP tag has a trailing newline */
|
||||
protected $prevCloseTagHasNewline;
|
||||
/** @var array<int, int> Map of tokens that should be dropped (like T_WHITESPACE) */
|
||||
protected $dropTokens;
|
||||
|
||||
/** @var bool Whether to use the startLine attribute */
|
||||
private $attributeStartLineUsed;
|
||||
/** @var bool Whether to use the endLine attribute */
|
||||
private $attributeEndLineUsed;
|
||||
/** @var bool Whether to use the startTokenPos attribute */
|
||||
private $attributeStartTokenPosUsed;
|
||||
/** @var bool Whether to use the endTokenPos attribute */
|
||||
private $attributeEndTokenPosUsed;
|
||||
/** @var bool Whether to use the startFilePos attribute */
|
||||
private $attributeStartFilePosUsed;
|
||||
/** @var bool Whether to use the endFilePos attribute */
|
||||
private $attributeEndFilePosUsed;
|
||||
/** @var bool Whether to use the comments attribute */
|
||||
private $attributeCommentsUsed;
|
||||
|
||||
/**
|
||||
* Creates a Lexer.
|
||||
* Tokenize the provided source code.
|
||||
*
|
||||
* @param array{usedAttributes?: string[]} $options Options array. Currently only the
|
||||
* 'usedAttributes' option is supported, which is an array of attributes to add to the
|
||||
* AST nodes. Possible attributes are: 'comments', 'startLine', 'endLine', 'startTokenPos',
|
||||
* 'endTokenPos', 'startFilePos', 'endFilePos'. The option defaults to the first three.
|
||||
* For more info see getNextToken() docs.
|
||||
*/
|
||||
public function __construct(array $options = []) {
|
||||
// map of tokens to drop while lexing (the map is only used for isset lookup,
|
||||
// that's why the value is simply set to 1; the value is never actually used.)
|
||||
$this->dropTokens = array_fill_keys(
|
||||
[\T_WHITESPACE, \T_OPEN_TAG, \T_COMMENT, \T_DOC_COMMENT, \T_BAD_CHARACTER], 1
|
||||
);
|
||||
|
||||
$defaultAttributes = ['comments', 'startLine', 'endLine'];
|
||||
$usedAttributes = array_fill_keys($options['usedAttributes'] ?? $defaultAttributes, true);
|
||||
|
||||
// Create individual boolean properties to make these checks faster.
|
||||
$this->attributeStartLineUsed = isset($usedAttributes['startLine']);
|
||||
$this->attributeEndLineUsed = isset($usedAttributes['endLine']);
|
||||
$this->attributeStartTokenPosUsed = isset($usedAttributes['startTokenPos']);
|
||||
$this->attributeEndTokenPosUsed = isset($usedAttributes['endTokenPos']);
|
||||
$this->attributeStartFilePosUsed = isset($usedAttributes['startFilePos']);
|
||||
$this->attributeEndFilePosUsed = isset($usedAttributes['endFilePos']);
|
||||
$this->attributeCommentsUsed = isset($usedAttributes['comments']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the lexer for lexing the provided source code.
|
||||
* The token array is in the same format as provided by the PhpToken::tokenize() method in
|
||||
* PHP 8.0. The tokens are instances of PhpParser\Token, to abstract over a polyfill
|
||||
* implementation in earlier PHP version.
|
||||
*
|
||||
* This function does not throw if lexing errors occur. Instead, errors may be retrieved using
|
||||
* the getErrors() method.
|
||||
* The token array is terminated by a sentinel token with token ID 0.
|
||||
* The token array does not discard any tokens (i.e. whitespace and comments are included).
|
||||
* The token position attributes are against this token array.
|
||||
*
|
||||
* @param string $code The source code to lex
|
||||
* @param string $code The source code to tokenize.
|
||||
* @param ErrorHandler|null $errorHandler Error handler to use for lexing errors. Defaults to
|
||||
* ErrorHandler\Throwing
|
||||
* ErrorHandler\Throwing.
|
||||
* @return Token[] Tokens
|
||||
*/
|
||||
public function startLexing(string $code, ?ErrorHandler $errorHandler = null): void {
|
||||
public function tokenize(string $code, ?ErrorHandler $errorHandler = null): array {
|
||||
if (null === $errorHandler) {
|
||||
$errorHandler = new ErrorHandler\Throwing();
|
||||
}
|
||||
|
||||
$this->code = $code; // keep the code around for __halt_compiler() handling
|
||||
$this->pos = -1;
|
||||
|
||||
// If inline HTML occurs without preceding code, treat it as if it had a leading newline.
|
||||
// This ensures proper composability, because having a newline is the "safe" assumption.
|
||||
$this->prevCloseTagHasNewline = true;
|
||||
|
||||
$scream = ini_set('xdebug.scream', '0');
|
||||
|
||||
$this->tokens = @Token::tokenize($code);
|
||||
$this->postprocessTokens($errorHandler);
|
||||
$tokens = @Token::tokenize($code);
|
||||
$this->postprocessTokens($tokens, $errorHandler);
|
||||
|
||||
if (false !== $scream) {
|
||||
ini_set('xdebug.scream', $scream);
|
||||
}
|
||||
|
||||
return $tokens;
|
||||
}
|
||||
|
||||
private function handleInvalidCharacter(Token $token, ErrorHandler $errorHandler): void {
|
||||
@ -117,33 +63,36 @@ class Lexer {
|
||||
&& substr($token->text, -2) !== '*/';
|
||||
}
|
||||
|
||||
protected function postprocessTokens(ErrorHandler $errorHandler): void {
|
||||
/**
|
||||
* @param list<Token> $tokens
|
||||
*/
|
||||
protected function postprocessTokens(array &$tokens, ErrorHandler $errorHandler): void {
|
||||
// This function reports errors (bad characters and unterminated comments) in the token
|
||||
// array, and performs certain canonicalizations:
|
||||
// * Use PHP 8.1 T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG and
|
||||
// T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG tokens used to disambiguate intersection types.
|
||||
// * Add a sentinel token with ID 0.
|
||||
|
||||
$numTokens = \count($this->tokens);
|
||||
$numTokens = \count($tokens);
|
||||
if ($numTokens === 0) {
|
||||
// Empty input edge case: Just add the sentinel token.
|
||||
$this->tokens[] = new Token(0, "\0", 1, 0);
|
||||
$tokens[] = new Token(0, "\0", 1, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
for ($i = 0; $i < $numTokens; $i++) {
|
||||
$token = $this->tokens[$i];
|
||||
$token = $tokens[$i];
|
||||
if ($token->id === \T_BAD_CHARACTER) {
|
||||
$this->handleInvalidCharacter($token, $errorHandler);
|
||||
}
|
||||
|
||||
if ($token->id === \ord('&')) {
|
||||
$next = $i + 1;
|
||||
while (isset($this->tokens[$next]) && $this->tokens[$next]->id === \T_WHITESPACE) {
|
||||
while (isset($tokens[$next]) && $tokens[$next]->id === \T_WHITESPACE) {
|
||||
$next++;
|
||||
}
|
||||
$followedByVarOrVarArg = isset($this->tokens[$next]) &&
|
||||
$this->tokens[$next]->is([\T_VARIABLE, \T_ELLIPSIS]);
|
||||
$followedByVarOrVarArg = isset($tokens[$next]) &&
|
||||
$tokens[$next]->is([\T_VARIABLE, \T_ELLIPSIS]);
|
||||
$token->id = $followedByVarOrVarArg
|
||||
? \T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG
|
||||
: \T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG;
|
||||
@ -151,7 +100,7 @@ class Lexer {
|
||||
}
|
||||
|
||||
// Check for unterminated comment
|
||||
$lastToken = $this->tokens[$numTokens - 1];
|
||||
$lastToken = $tokens[$numTokens - 1];
|
||||
if ($this->isUnterminatedComment($lastToken)) {
|
||||
$errorHandler->handleError(new Error('Unterminated comment', [
|
||||
'startLine' => $lastToken->line,
|
||||
@ -162,115 +111,6 @@ class Lexer {
|
||||
}
|
||||
|
||||
// Add sentinel token.
|
||||
$this->tokens[] = new Token(0, "\0", $lastToken->getEndLine(), $lastToken->getEndPos());
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the next token.
|
||||
*
|
||||
* The available attributes are determined by the 'usedAttributes' option, which can
|
||||
* be specified in the constructor. The following attributes are supported:
|
||||
*
|
||||
* * 'comments' => Array of PhpParser\Comment or PhpParser\Comment\Doc instances,
|
||||
* representing all comments that occurred between the previous
|
||||
* non-discarded token and the current one.
|
||||
* * 'startLine' => Line in which the node starts.
|
||||
* * 'endLine' => Line in which the node ends.
|
||||
* * 'startTokenPos' => Offset into the token array of the first token in the node.
|
||||
* * 'endTokenPos' => Offset into the token array of the last token in the node.
|
||||
* * 'startFilePos' => Offset into the code string of the first character that is part of the node.
|
||||
* * 'endFilePos' => Offset into the code string of the last character that is part of the node.
|
||||
*
|
||||
* @param mixed $value Variable to store token content in
|
||||
* @param mixed $startAttributes Variable to store start attributes in
|
||||
* @param mixed $endAttributes Variable to store end attributes in
|
||||
*
|
||||
* @return int Token id
|
||||
*/
|
||||
public function getNextToken(&$value = null, &$startAttributes = null, &$endAttributes = null): int {
|
||||
$startAttributes = [];
|
||||
$endAttributes = [];
|
||||
|
||||
while (1) {
|
||||
$token = $this->tokens[++$this->pos];
|
||||
|
||||
if ($this->attributeStartLineUsed) {
|
||||
$startAttributes['startLine'] = $token->line;
|
||||
}
|
||||
if ($this->attributeStartTokenPosUsed) {
|
||||
$startAttributes['startTokenPos'] = $this->pos;
|
||||
}
|
||||
if ($this->attributeStartFilePosUsed) {
|
||||
$startAttributes['startFilePos'] = $token->pos;
|
||||
}
|
||||
|
||||
$id = $token->id;
|
||||
if (isset($this->dropTokens[$id])) {
|
||||
if (\T_COMMENT === $id || \T_DOC_COMMENT === $id) {
|
||||
if ($this->attributeCommentsUsed) {
|
||||
$comment = \T_DOC_COMMENT === $id
|
||||
? new Comment\Doc($token->text, $token->line, $token->pos, $this->pos,
|
||||
$token->getEndLine(), $token->getEndPos() - 1, $this->pos)
|
||||
: new Comment($token->text, $token->line, $token->pos, $this->pos,
|
||||
$token->getEndLine(), $token->getEndPos() - 1, $this->pos);
|
||||
$startAttributes['comments'][] = $comment;
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
$value = $token->text;
|
||||
if (\T_CLOSE_TAG === $token->id) {
|
||||
$this->prevCloseTagHasNewline = false !== strpos($value, "\n")
|
||||
|| false !== strpos($value, "\r");
|
||||
} elseif (\T_INLINE_HTML === $token->id) {
|
||||
$startAttributes['hasLeadingNewline'] = $this->prevCloseTagHasNewline;
|
||||
}
|
||||
|
||||
// Fetch the end line/pos from the next token (if available) instead of recomputing it.
|
||||
$nextToken = $this->tokens[$this->pos + 1] ?? null;
|
||||
if ($this->attributeEndLineUsed) {
|
||||
$endAttributes['endLine'] = $nextToken ? $nextToken->line : $token->getEndLine();
|
||||
}
|
||||
if ($this->attributeEndTokenPosUsed) {
|
||||
$endAttributes['endTokenPos'] = $this->pos;
|
||||
}
|
||||
if ($this->attributeEndFilePosUsed) {
|
||||
$endAttributes['endFilePos'] = ($nextToken ? $nextToken->pos : $token->getEndPos()) - 1;
|
||||
}
|
||||
|
||||
return $id;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the token array for current code.
|
||||
*
|
||||
* The token array is in the same format as provided by the PhpToken::tokenize() method in
|
||||
* PHP 8.0. The tokens are instances of PhpParser\Token, to abstract over a polyfill
|
||||
* implementation in earlier PHP version.
|
||||
*
|
||||
* The token array is terminated by a sentinel token with token ID 0.
|
||||
* The token array does not discard any tokens (i.e. whitespace and comments are included).
|
||||
* The token position attributes are against this token array.
|
||||
*
|
||||
* @return Token[] Array of tokens
|
||||
*/
|
||||
public function getTokens(): array {
|
||||
return $this->tokens;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles __halt_compiler() by returning the text after it.
|
||||
*
|
||||
* @return string Remaining text
|
||||
*/
|
||||
public function handleHaltCompiler(): string {
|
||||
// Prevent the lexer from returning any further tokens.
|
||||
$nextToken = $this->tokens[$this->pos + 1];
|
||||
$this->pos = \count($this->tokens) - 2;
|
||||
|
||||
// Return text after __halt_compiler.
|
||||
return $nextToken->id === \T_INLINE_HTML ? $nextToken->text : '';
|
||||
$tokens[] = new Token(0, "\0", $lastToken->getEndLine(), $lastToken->getEndPos());
|
||||
}
|
||||
}
|
||||
|
@ -5,61 +5,48 @@ namespace PhpParser\Lexer;
|
||||
use PhpParser\Error;
|
||||
use PhpParser\ErrorHandler;
|
||||
use PhpParser\Lexer;
|
||||
use PhpParser\Lexer\TokenEmulator\AsymmetricVisibilityTokenEmulator;
|
||||
use PhpParser\Lexer\TokenEmulator\AttributeEmulator;
|
||||
use PhpParser\Lexer\TokenEmulator\EnumTokenEmulator;
|
||||
use PhpParser\Lexer\TokenEmulator\CoaleseEqualTokenEmulator;
|
||||
use PhpParser\Lexer\TokenEmulator\ExplicitOctalEmulator;
|
||||
use PhpParser\Lexer\TokenEmulator\FlexibleDocStringEmulator;
|
||||
use PhpParser\Lexer\TokenEmulator\FnTokenEmulator;
|
||||
use PhpParser\Lexer\TokenEmulator\MatchTokenEmulator;
|
||||
use PhpParser\Lexer\TokenEmulator\NullsafeTokenEmulator;
|
||||
use PhpParser\Lexer\TokenEmulator\NumericLiteralSeparatorEmulator;
|
||||
use PhpParser\Lexer\TokenEmulator\PropertyTokenEmulator;
|
||||
use PhpParser\Lexer\TokenEmulator\ReadonlyFunctionTokenEmulator;
|
||||
use PhpParser\Lexer\TokenEmulator\ReadonlyTokenEmulator;
|
||||
use PhpParser\Lexer\TokenEmulator\ReverseEmulator;
|
||||
use PhpParser\Lexer\TokenEmulator\TokenEmulator;
|
||||
use PhpParser\PhpVersion;
|
||||
use PhpParser\Token;
|
||||
|
||||
class Emulative extends Lexer {
|
||||
/** @var array{int, string, string}[] Patches used to reverse changes introduced in the code */
|
||||
private $patches = [];
|
||||
private array $patches = [];
|
||||
|
||||
/** @var list<TokenEmulator> */
|
||||
private $emulators = [];
|
||||
private array $emulators = [];
|
||||
|
||||
/** @var PhpVersion */
|
||||
private $targetPhpVersion;
|
||||
/** @var PhpVersion */
|
||||
private $hostPhpVersion;
|
||||
private PhpVersion $targetPhpVersion;
|
||||
|
||||
private PhpVersion $hostPhpVersion;
|
||||
|
||||
/**
|
||||
* @param array{usedAttributes?: string[], phpVersion?: PhpVersion|string} $options Lexer options.
|
||||
* In addition to the usual options, accepts a 'phpVersion' (PhpVersion object or string)
|
||||
* that specifies the version to emulate. Defaults to newest supported.
|
||||
* @param PhpVersion|null $phpVersion PHP version to emulate. Defaults to newest supported.
|
||||
*/
|
||||
public function __construct(array $options = []) {
|
||||
$version = $options['phpVersion'] ?? PhpVersion::getNewestSupported();
|
||||
if (!$version instanceof PhpVersion) {
|
||||
$version = PhpVersion::fromString($version);
|
||||
}
|
||||
$this->targetPhpVersion = $version;
|
||||
public function __construct(?PhpVersion $phpVersion = null) {
|
||||
$this->targetPhpVersion = $phpVersion ?? PhpVersion::getNewestSupported();
|
||||
$this->hostPhpVersion = PhpVersion::getHostVersion();
|
||||
unset($options['phpVersion']);
|
||||
|
||||
parent::__construct($options);
|
||||
|
||||
$emulators = [
|
||||
new FlexibleDocStringEmulator(),
|
||||
new FnTokenEmulator(),
|
||||
new MatchTokenEmulator(),
|
||||
new CoaleseEqualTokenEmulator(),
|
||||
new NumericLiteralSeparatorEmulator(),
|
||||
new NullsafeTokenEmulator(),
|
||||
new AttributeEmulator(),
|
||||
new EnumTokenEmulator(),
|
||||
new ReadonlyTokenEmulator(),
|
||||
new ExplicitOctalEmulator(),
|
||||
new ReadonlyFunctionTokenEmulator(),
|
||||
new PropertyTokenEmulator(),
|
||||
new AsymmetricVisibilityTokenEmulator(),
|
||||
];
|
||||
|
||||
// Collect emulators that are relevant for the PHP version we're running
|
||||
@ -74,15 +61,18 @@ class Emulative extends Lexer {
|
||||
}
|
||||
}
|
||||
|
||||
public function startLexing(string $code, ?ErrorHandler $errorHandler = null): void {
|
||||
public function tokenize(string $code, ?ErrorHandler $errorHandler = null): array {
|
||||
$emulators = array_filter($this->emulators, function ($emulator) use ($code) {
|
||||
return $emulator->isEmulationNeeded($code);
|
||||
});
|
||||
|
||||
if (empty($emulators)) {
|
||||
// Nothing to emulate, yay
|
||||
parent::startLexing($code, $errorHandler);
|
||||
return;
|
||||
return parent::tokenize($code, $errorHandler);
|
||||
}
|
||||
|
||||
if ($errorHandler === null) {
|
||||
$errorHandler = new ErrorHandler\Throwing();
|
||||
}
|
||||
|
||||
$this->patches = [];
|
||||
@ -91,9 +81,9 @@ class Emulative extends Lexer {
|
||||
}
|
||||
|
||||
$collector = new ErrorHandler\Collecting();
|
||||
parent::startLexing($code, $collector);
|
||||
$tokens = parent::tokenize($code, $collector);
|
||||
$this->sortPatches();
|
||||
$this->fixupTokens();
|
||||
$tokens = $this->fixupTokens($tokens);
|
||||
|
||||
$errors = $collector->getErrors();
|
||||
if (!empty($errors)) {
|
||||
@ -104,8 +94,10 @@ class Emulative extends Lexer {
|
||||
}
|
||||
|
||||
foreach ($emulators as $emulator) {
|
||||
$this->tokens = $emulator->emulate($code, $this->tokens);
|
||||
$tokens = $emulator->emulate($code, $tokens);
|
||||
}
|
||||
|
||||
return $tokens;
|
||||
}
|
||||
|
||||
private function isForwardEmulationNeeded(PhpVersion $emulatorPhpVersion): bool {
|
||||
@ -126,9 +118,13 @@ class Emulative extends Lexer {
|
||||
});
|
||||
}
|
||||
|
||||
private function fixupTokens(): void {
|
||||
/**
|
||||
* @param list<Token> $tokens
|
||||
* @return list<Token>
|
||||
*/
|
||||
private function fixupTokens(array $tokens): array {
|
||||
if (\count($this->patches) === 0) {
|
||||
return;
|
||||
return $tokens;
|
||||
}
|
||||
|
||||
// Load first patch
|
||||
@ -137,10 +133,12 @@ class Emulative extends Lexer {
|
||||
|
||||
// We use a manual loop over the tokens, because we modify the array on the fly
|
||||
$posDelta = 0;
|
||||
for ($i = 0, $c = \count($this->tokens); $i < $c; $i++) {
|
||||
$token = $this->tokens[$i];
|
||||
$lineDelta = 0;
|
||||
for ($i = 0, $c = \count($tokens); $i < $c; $i++) {
|
||||
$token = $tokens[$i];
|
||||
$pos = $token->pos;
|
||||
$token->pos += $posDelta;
|
||||
$token->line += $lineDelta;
|
||||
$localPosDelta = 0;
|
||||
$len = \strlen($token->text);
|
||||
while ($patchPos >= $pos && $patchPos < $pos + $len) {
|
||||
@ -148,7 +146,7 @@ class Emulative extends Lexer {
|
||||
if ($patchType === 'remove') {
|
||||
if ($patchPos === $pos && $patchTextLen === $len) {
|
||||
// Remove token entirely
|
||||
array_splice($this->tokens, $i, 1, []);
|
||||
array_splice($tokens, $i, 1, []);
|
||||
$i--;
|
||||
$c--;
|
||||
} else {
|
||||
@ -158,12 +156,14 @@ class Emulative extends Lexer {
|
||||
);
|
||||
$localPosDelta -= $patchTextLen;
|
||||
}
|
||||
$lineDelta -= \substr_count($patchText, "\n");
|
||||
} elseif ($patchType === 'add') {
|
||||
// Insert into the token string
|
||||
$token->text = substr_replace(
|
||||
$token->text, $patchText, $patchPos - $pos + $localPosDelta, 0
|
||||
);
|
||||
$localPosDelta += $patchTextLen;
|
||||
$lineDelta += \substr_count($patchText, "\n");
|
||||
} elseif ($patchType === 'replace') {
|
||||
// Replace inside the token string
|
||||
$token->text = substr_replace(
|
||||
@ -186,6 +186,7 @@ class Emulative extends Lexer {
|
||||
|
||||
$posDelta += $localPosDelta;
|
||||
}
|
||||
return $tokens;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -1,40 +0,0 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser\Lexer\TokenEmulator;
|
||||
|
||||
use PhpParser\PhpVersion;
|
||||
use PhpParser\Token;
|
||||
|
||||
final class CoaleseEqualTokenEmulator extends TokenEmulator {
|
||||
public function getPhpVersion(): PhpVersion {
|
||||
return PhpVersion::fromComponents(7, 4);
|
||||
}
|
||||
|
||||
public function isEmulationNeeded(string $code): bool {
|
||||
return strpos($code, '??=') !== false;
|
||||
}
|
||||
|
||||
public function emulate(string $code, array $tokens): array {
|
||||
// We need to manually iterate and manage a count because we'll change
|
||||
// the tokens array on the way
|
||||
for ($i = 0, $c = count($tokens); $i < $c; ++$i) {
|
||||
$token = $tokens[$i];
|
||||
if (isset($tokens[$i + 1])) {
|
||||
if ($token->id === T_COALESCE && $tokens[$i + 1]->text === '=') {
|
||||
array_splice($tokens, $i, 2, [
|
||||
new Token(\T_COALESCE_EQUAL, '??=', $token->line, $token->pos),
|
||||
]);
|
||||
$c--;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $tokens;
|
||||
}
|
||||
|
||||
public function reverseEmulate(string $code, array $tokens): array {
|
||||
// ??= was not valid code previously, don't bother.
|
||||
return $tokens;
|
||||
}
|
||||
}
|
@ -1,71 +0,0 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser\Lexer\TokenEmulator;
|
||||
|
||||
use PhpParser\PhpVersion;
|
||||
|
||||
final class FlexibleDocStringEmulator extends TokenEmulator {
|
||||
private const FLEXIBLE_DOC_STRING_REGEX = <<<'REGEX'
|
||||
/<<<[ \t]*(['"]?)([a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*)\1\r?\n
|
||||
(?:.*\r?\n)*?
|
||||
(?<indentation>\h*)\2(?![a-zA-Z0-9_\x80-\xff])(?<separator>(?:;?[\r\n])?)/x
|
||||
REGEX;
|
||||
|
||||
public function getPhpVersion(): PhpVersion {
|
||||
return PhpVersion::fromComponents(7, 3);
|
||||
}
|
||||
|
||||
public function isEmulationNeeded(string $code): bool {
|
||||
return strpos($code, '<<<') !== false;
|
||||
}
|
||||
|
||||
public function emulate(string $code, array $tokens): array {
|
||||
// Handled by preprocessing + fixup.
|
||||
return $tokens;
|
||||
}
|
||||
|
||||
public function reverseEmulate(string $code, array $tokens): array {
|
||||
// Not supported.
|
||||
return $tokens;
|
||||
}
|
||||
|
||||
public function preprocessCode(string $code, array &$patches): string {
|
||||
if (!preg_match_all(self::FLEXIBLE_DOC_STRING_REGEX, $code, $matches, PREG_SET_ORDER|PREG_OFFSET_CAPTURE)) {
|
||||
// No heredoc/nowdoc found
|
||||
return $code;
|
||||
}
|
||||
|
||||
// Keep track of how much we need to adjust string offsets due to the modifications we
|
||||
// already made
|
||||
$posDelta = 0;
|
||||
foreach ($matches as $match) {
|
||||
$indentation = $match['indentation'][0];
|
||||
$indentationStart = $match['indentation'][1];
|
||||
|
||||
$separator = $match['separator'][0];
|
||||
$separatorStart = $match['separator'][1];
|
||||
|
||||
if ($indentation === '' && $separator !== '') {
|
||||
// Ordinary heredoc/nowdoc
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($indentation !== '') {
|
||||
// Remove indentation
|
||||
$indentationLen = strlen($indentation);
|
||||
$code = substr_replace($code, '', $indentationStart + $posDelta, $indentationLen);
|
||||
$patches[] = [$indentationStart + $posDelta, 'add', $indentation];
|
||||
$posDelta -= $indentationLen;
|
||||
}
|
||||
|
||||
if ($separator === '') {
|
||||
// Insert newline as separator
|
||||
$code = substr_replace($code, "\n", $separatorStart + $posDelta, 0);
|
||||
$patches[] = [$separatorStart + $posDelta, 'remove', "\n"];
|
||||
$posDelta += 1;
|
||||
}
|
||||
}
|
||||
|
||||
return $code;
|
||||
}
|
||||
}
|
@ -14,8 +14,12 @@ abstract class KeywordEmulator extends TokenEmulator {
|
||||
|
||||
/** @param Token[] $tokens */
|
||||
protected function isKeywordContext(array $tokens, int $pos): bool {
|
||||
$previousNonSpaceToken = $this->getPreviousNonSpaceToken($tokens, $pos);
|
||||
return $previousNonSpaceToken === null || $previousNonSpaceToken->id !== \T_OBJECT_OPERATOR;
|
||||
$prevToken = $this->getPreviousNonSpaceToken($tokens, $pos);
|
||||
if ($prevToken === null) {
|
||||
return false;
|
||||
}
|
||||
return $prevToken->id !== \T_OBJECT_OPERATOR
|
||||
&& $prevToken->id !== \T_NULLSAFE_OBJECT_OPERATOR;
|
||||
}
|
||||
|
||||
public function emulate(string $code, array $tokens): array {
|
||||
@ -45,7 +49,7 @@ abstract class KeywordEmulator extends TokenEmulator {
|
||||
|
||||
public function reverseEmulate(string $code, array $tokens): array {
|
||||
$keywordToken = $this->getKeywordToken();
|
||||
foreach ($tokens as $i => $token) {
|
||||
foreach ($tokens as $token) {
|
||||
if ($token->id === $keywordToken) {
|
||||
$token->id = \T_STRING;
|
||||
}
|
||||
|
@ -1,95 +0,0 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser\Lexer\TokenEmulator;
|
||||
|
||||
use PhpParser\PhpVersion;
|
||||
use PhpParser\Token;
|
||||
|
||||
final class NumericLiteralSeparatorEmulator extends TokenEmulator {
|
||||
private const BIN = '(?:0b[01]+(?:_[01]+)*)';
|
||||
private const HEX = '(?:0x[0-9a-f]+(?:_[0-9a-f]+)*)';
|
||||
private const DEC = '(?:[0-9]+(?:_[0-9]+)*)';
|
||||
private const SIMPLE_FLOAT = '(?:' . self::DEC . '\.' . self::DEC . '?|\.' . self::DEC . ')';
|
||||
private const EXP = '(?:e[+-]?' . self::DEC . ')';
|
||||
private const FLOAT = '(?:' . self::SIMPLE_FLOAT . self::EXP . '?|' . self::DEC . self::EXP . ')';
|
||||
private const NUMBER = '~' . self::FLOAT . '|' . self::BIN . '|' . self::HEX . '|' . self::DEC . '~iA';
|
||||
|
||||
public function getPhpVersion(): PhpVersion {
|
||||
return PhpVersion::fromComponents(7, 4);
|
||||
}
|
||||
|
||||
public function isEmulationNeeded(string $code): bool {
|
||||
return preg_match('~[0-9]_[0-9]~', $code)
|
||||
|| preg_match('~0x[0-9a-f]+_[0-9a-f]~i', $code);
|
||||
}
|
||||
|
||||
public function emulate(string $code, array $tokens): array {
|
||||
// We need to manually iterate and manage a count because we'll change
|
||||
// the tokens array on the way
|
||||
for ($i = 0, $c = count($tokens); $i < $c; ++$i) {
|
||||
$token = $tokens[$i];
|
||||
$tokenLen = \strlen($token->text);
|
||||
|
||||
if ($token->id !== \T_LNUMBER && $token->id !== \T_DNUMBER) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$res = preg_match(self::NUMBER, $code, $matches, 0, $token->pos);
|
||||
assert($res, "No number at number token position");
|
||||
|
||||
$match = $matches[0];
|
||||
$matchLen = \strlen($match);
|
||||
if ($matchLen === $tokenLen) {
|
||||
// Original token already holds the full number.
|
||||
continue;
|
||||
}
|
||||
|
||||
$tokenKind = $this->resolveIntegerOrFloatToken($match);
|
||||
$newTokens = [new Token($tokenKind, $match, $token->line, $token->pos)];
|
||||
|
||||
$numTokens = 1;
|
||||
$len = $tokenLen;
|
||||
while ($matchLen > $len) {
|
||||
$nextToken = $tokens[$i + $numTokens];
|
||||
$nextTokenText = $nextToken->text;
|
||||
$nextTokenLen = \strlen($nextTokenText);
|
||||
|
||||
$numTokens++;
|
||||
if ($matchLen < $len + $nextTokenLen) {
|
||||
// Split trailing characters into a partial token.
|
||||
$partialText = substr($nextTokenText, $matchLen - $len);
|
||||
$newTokens[] = new Token($nextToken->id, $partialText, $nextToken->line, $nextToken->pos);
|
||||
break;
|
||||
}
|
||||
|
||||
$len += $nextTokenLen;
|
||||
}
|
||||
|
||||
array_splice($tokens, $i, $numTokens, $newTokens);
|
||||
$c -= $numTokens - \count($newTokens);
|
||||
}
|
||||
|
||||
return $tokens;
|
||||
}
|
||||
|
||||
private function resolveIntegerOrFloatToken(string $str): int {
|
||||
$str = str_replace('_', '', $str);
|
||||
|
||||
if (stripos($str, '0b') === 0) {
|
||||
$num = bindec($str);
|
||||
} elseif (stripos($str, '0x') === 0) {
|
||||
$num = hexdec($str);
|
||||
} elseif (stripos($str, '0') === 0 && ctype_digit($str)) {
|
||||
$num = octdec($str);
|
||||
} else {
|
||||
$num = +$str;
|
||||
}
|
||||
|
||||
return is_float($num) ? T_DNUMBER : T_LNUMBER;
|
||||
}
|
||||
|
||||
public function reverseEmulate(string $code, array $tokens): array {
|
||||
// Numeric separators were not legal code previously, don't bother.
|
||||
return $tokens;
|
||||
}
|
||||
}
|
@ -4,16 +4,16 @@ namespace PhpParser\Lexer\TokenEmulator;
|
||||
|
||||
use PhpParser\PhpVersion;
|
||||
|
||||
final class FnTokenEmulator extends KeywordEmulator {
|
||||
final class PropertyTokenEmulator extends KeywordEmulator {
|
||||
public function getPhpVersion(): PhpVersion {
|
||||
return PhpVersion::fromComponents(7, 4);
|
||||
return PhpVersion::fromComponents(8, 4);
|
||||
}
|
||||
|
||||
public function getKeywordString(): string {
|
||||
return 'fn';
|
||||
return '__property__';
|
||||
}
|
||||
|
||||
public function getKeywordToken(): int {
|
||||
return \T_FN;
|
||||
return \T_PROPERTY_C;
|
||||
}
|
||||
}
|
@ -9,7 +9,7 @@ use PhpParser\PhpVersion;
|
||||
*/
|
||||
final class ReverseEmulator extends TokenEmulator {
|
||||
/** @var TokenEmulator Inner emulator */
|
||||
private $emulator;
|
||||
private TokenEmulator $emulator;
|
||||
|
||||
public function __construct(TokenEmulator $emulator) {
|
||||
$this->emulator = $emulator;
|
||||
|
@ -14,23 +14,47 @@ final class Modifiers {
|
||||
public const ABSTRACT = 16;
|
||||
public const FINAL = 32;
|
||||
public const READONLY = 64;
|
||||
public const PUBLIC_SET = 128;
|
||||
public const PROTECTED_SET = 256;
|
||||
public const PRIVATE_SET = 512;
|
||||
|
||||
public const VISIBILITY_MASK = 1 | 2 | 4;
|
||||
public const VISIBILITY_MASK = self::PUBLIC | self::PROTECTED | self::PRIVATE;
|
||||
|
||||
public const VISIBILITY_SET_MASK = self::PUBLIC_SET | self::PROTECTED_SET | self::PRIVATE_SET;
|
||||
|
||||
private const TO_STRING_MAP = [
|
||||
self::PUBLIC => 'public',
|
||||
self::PROTECTED => 'protected',
|
||||
self::PRIVATE => 'private',
|
||||
self::STATIC => 'static',
|
||||
self::ABSTRACT => 'abstract',
|
||||
self::FINAL => 'final',
|
||||
self::READONLY => 'readonly',
|
||||
self::PUBLIC_SET => 'public(set)',
|
||||
self::PROTECTED_SET => 'protected(set)',
|
||||
self::PRIVATE_SET => 'private(set)',
|
||||
];
|
||||
|
||||
public static function toString(int $modifier): string {
|
||||
if (!isset(self::TO_STRING_MAP[$modifier])) {
|
||||
throw new \InvalidArgumentException("Unknown modifier $modifier");
|
||||
}
|
||||
return self::TO_STRING_MAP[$modifier];
|
||||
}
|
||||
|
||||
private static function isValidModifier(int $modifier): bool {
|
||||
$isPow2 = ($modifier & ($modifier - 1)) == 0 && $modifier != 0;
|
||||
return $isPow2 && $modifier <= self::PRIVATE_SET;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public static function verifyClassModifier(int $a, int $b): void {
|
||||
if ($a & Modifiers::ABSTRACT && $b & Modifiers::ABSTRACT) {
|
||||
throw new Error('Multiple abstract modifiers are not allowed');
|
||||
}
|
||||
|
||||
if ($a & Modifiers::FINAL && $b & Modifiers::FINAL) {
|
||||
throw new Error('Multiple final modifiers are not allowed');
|
||||
}
|
||||
|
||||
if ($a & Modifiers::READONLY && $b & Modifiers::READONLY) {
|
||||
throw new Error('Multiple readonly modifiers are not allowed');
|
||||
assert(self::isValidModifier($b));
|
||||
if (($a & $b) != 0) {
|
||||
throw new Error(
|
||||
'Multiple ' . self::toString($b) . ' modifiers are not allowed');
|
||||
}
|
||||
|
||||
if ($a & 48 && $b & 48) {
|
||||
@ -42,24 +66,16 @@ final class Modifiers {
|
||||
* @internal
|
||||
*/
|
||||
public static function verifyModifier(int $a, int $b): void {
|
||||
if ($a & Modifiers::VISIBILITY_MASK && $b & Modifiers::VISIBILITY_MASK) {
|
||||
assert(self::isValidModifier($b));
|
||||
if (($a & Modifiers::VISIBILITY_MASK && $b & Modifiers::VISIBILITY_MASK) ||
|
||||
($a & Modifiers::VISIBILITY_SET_MASK && $b & Modifiers::VISIBILITY_SET_MASK)
|
||||
) {
|
||||
throw new Error('Multiple access type modifiers are not allowed');
|
||||
}
|
||||
|
||||
if ($a & Modifiers::ABSTRACT && $b & Modifiers::ABSTRACT) {
|
||||
throw new Error('Multiple abstract modifiers are not allowed');
|
||||
}
|
||||
|
||||
if ($a & Modifiers::STATIC && $b & Modifiers::STATIC) {
|
||||
throw new Error('Multiple static modifiers are not allowed');
|
||||
}
|
||||
|
||||
if ($a & Modifiers::FINAL && $b & Modifiers::FINAL) {
|
||||
throw new Error('Multiple final modifiers are not allowed');
|
||||
}
|
||||
|
||||
if ($a & Modifiers::READONLY && $b & Modifiers::READONLY) {
|
||||
throw new Error('Multiple readonly modifiers are not allowed');
|
||||
if (($a & $b) != 0) {
|
||||
throw new Error(
|
||||
'Multiple ' . self::toString($b) . ' modifiers are not allowed');
|
||||
}
|
||||
|
||||
if ($a & 48 && $b & 48) {
|
||||
|
@ -8,16 +8,16 @@ use PhpParser\Node\Stmt;
|
||||
|
||||
class NameContext {
|
||||
/** @var null|Name Current namespace */
|
||||
protected $namespace;
|
||||
protected ?Name $namespace;
|
||||
|
||||
/** @var Name[][] Map of format [aliasType => [aliasName => originalName]] */
|
||||
protected $aliases = [];
|
||||
protected array $aliases = [];
|
||||
|
||||
/** @var Name[][] Same as $aliases but preserving original case */
|
||||
protected $origAliases = [];
|
||||
protected array $origAliases = [];
|
||||
|
||||
/** @var ErrorHandler Error handler */
|
||||
protected $errorHandler;
|
||||
protected ErrorHandler $errorHandler;
|
||||
|
||||
/**
|
||||
* Create a name context.
|
||||
@ -47,9 +47,9 @@ class NameContext {
|
||||
/**
|
||||
* Add an alias / import.
|
||||
*
|
||||
* @param Name $name Original name
|
||||
* @param string $aliasName Aliased name
|
||||
* @param int $type One of Stmt\Use_::TYPE_*
|
||||
* @param Name $name Original name
|
||||
* @param string $aliasName Aliased name
|
||||
* @param Stmt\Use_::TYPE_* $type One of Stmt\Use_::TYPE_*
|
||||
* @param array<string, mixed> $errorAttrs Attributes to use to report an error
|
||||
*/
|
||||
public function addAlias(Name $name, string $aliasName, int $type, array $errorAttrs = []): void {
|
||||
@ -94,7 +94,7 @@ class NameContext {
|
||||
* Get resolved name.
|
||||
*
|
||||
* @param Name $name Name to resolve
|
||||
* @param int $type One of Stmt\Use_::TYPE_{FUNCTION|CONSTANT}
|
||||
* @param Stmt\Use_::TYPE_* $type One of Stmt\Use_::TYPE_{FUNCTION|CONSTANT}
|
||||
*
|
||||
* @return null|Name Resolved name, or null if static resolution is not possible
|
||||
*/
|
||||
@ -149,7 +149,7 @@ class NameContext {
|
||||
* Get possible ways of writing a fully qualified name (e.g., by making use of aliases).
|
||||
*
|
||||
* @param string $name Fully-qualified name (without leading namespace separator)
|
||||
* @param int $type One of Stmt\Use_::TYPE_*
|
||||
* @param Stmt\Use_::TYPE_* $type One of Stmt\Use_::TYPE_*
|
||||
*
|
||||
* @return Name[] Possible representations of the name
|
||||
*/
|
||||
@ -185,7 +185,7 @@ class NameContext {
|
||||
// Check for relevant type-specific use statements
|
||||
foreach ($this->origAliases[$type] as $alias => $orig) {
|
||||
if ($type === Stmt\Use_::TYPE_CONSTANT) {
|
||||
// Constants are are complicated-sensitive
|
||||
// Constants are complicated-sensitive
|
||||
$normalizedOrig = $this->normalizeConstName($orig->toString());
|
||||
if ($normalizedOrig === $this->normalizeConstName($name)) {
|
||||
$possibleNames[] = new Name($alias);
|
||||
@ -205,7 +205,7 @@ class NameContext {
|
||||
* Get shortest representation of this fully-qualified name.
|
||||
*
|
||||
* @param string $name Fully-qualified name (without leading namespace separator)
|
||||
* @param int $type One of Stmt\Use_::TYPE_*
|
||||
* @param Stmt\Use_::TYPE_* $type One of Stmt\Use_::TYPE_*
|
||||
*
|
||||
* @return Name Shortest representation
|
||||
*/
|
||||
|
@ -6,6 +6,7 @@ interface Node {
|
||||
/**
|
||||
* Gets the type of the node.
|
||||
*
|
||||
* @psalm-return non-empty-string
|
||||
* @return string Type of the node
|
||||
*/
|
||||
public function getType(): string;
|
||||
@ -21,6 +22,9 @@ interface Node {
|
||||
* Gets line the node started in (alias of getStartLine).
|
||||
*
|
||||
* @return int Start line (or -1 if not available)
|
||||
* @phpstan-return -1|positive-int
|
||||
*
|
||||
* @deprecated Use getStartLine() instead
|
||||
*/
|
||||
public function getLine(): int;
|
||||
|
||||
@ -30,6 +34,7 @@ interface Node {
|
||||
* Requires the 'startLine' attribute to be enabled in the lexer (enabled by default).
|
||||
*
|
||||
* @return int Start line (or -1 if not available)
|
||||
* @phpstan-return -1|positive-int
|
||||
*/
|
||||
public function getStartLine(): int;
|
||||
|
||||
@ -39,6 +44,7 @@ interface Node {
|
||||
* Requires the 'endLine' attribute to be enabled in the lexer (enabled by default).
|
||||
*
|
||||
* @return int End line (or -1 if not available)
|
||||
* @phpstan-return -1|positive-int
|
||||
*/
|
||||
public function getEndLine(): int;
|
||||
|
||||
@ -110,25 +116,19 @@ interface Node {
|
||||
/**
|
||||
* Sets an attribute on a node.
|
||||
*
|
||||
* @param string $key
|
||||
* @param mixed $value
|
||||
* @param mixed $value
|
||||
*/
|
||||
public function setAttribute(string $key, $value): void;
|
||||
|
||||
/**
|
||||
* Returns whether an attribute exists.
|
||||
*
|
||||
* @param string $key
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasAttribute(string $key): bool;
|
||||
|
||||
/**
|
||||
* Returns the value of an attribute.
|
||||
*
|
||||
* @param string $key
|
||||
* @param mixed $default
|
||||
* @param mixed $default
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
|
@ -6,20 +6,20 @@ use PhpParser\NodeAbstract;
|
||||
|
||||
class Arg extends NodeAbstract {
|
||||
/** @var Identifier|null Parameter name (for named parameters) */
|
||||
public $name;
|
||||
public ?Identifier $name;
|
||||
/** @var Expr Value to pass */
|
||||
public $value;
|
||||
public Expr $value;
|
||||
/** @var bool Whether to pass by ref */
|
||||
public $byRef;
|
||||
public bool $byRef;
|
||||
/** @var bool Whether to unpack the argument */
|
||||
public $unpack;
|
||||
public bool $unpack;
|
||||
|
||||
/**
|
||||
* Constructs a function call argument node.
|
||||
*
|
||||
* @param Expr $value Value to pass
|
||||
* @param bool $byRef Whether to pass by ref
|
||||
* @param bool $unpack Whether to unpack the argument
|
||||
* @param Expr $value Value to pass
|
||||
* @param bool $byRef Whether to pass by ref
|
||||
* @param bool $unpack Whether to unpack the argument
|
||||
* @param array<string, mixed> $attributes Additional attributes
|
||||
* @param Identifier|null $name Parameter name (for named parameters)
|
||||
*/
|
||||
|
@ -6,20 +6,20 @@ use PhpParser\NodeAbstract;
|
||||
|
||||
class ArrayItem extends NodeAbstract {
|
||||
/** @var null|Expr Key */
|
||||
public $key;
|
||||
public ?Expr $key;
|
||||
/** @var Expr Value */
|
||||
public $value;
|
||||
public Expr $value;
|
||||
/** @var bool Whether to assign by reference */
|
||||
public $byRef;
|
||||
public bool $byRef;
|
||||
/** @var bool Whether to unpack the argument */
|
||||
public $unpack;
|
||||
public bool $unpack;
|
||||
|
||||
/**
|
||||
* Constructs an array item node.
|
||||
*
|
||||
* @param Expr $value Value
|
||||
* @param null|Expr $key Key
|
||||
* @param bool $byRef Whether to assign by reference
|
||||
* @param Expr $value Value
|
||||
* @param null|Expr $key Key
|
||||
* @param bool $byRef Whether to assign by reference
|
||||
* @param array<string, mixed> $attributes Additional attributes
|
||||
*/
|
||||
public function __construct(Expr $value, ?Expr $key = null, bool $byRef = false, array $attributes = [], bool $unpack = false) {
|
||||
|
@ -7,13 +7,13 @@ use PhpParser\NodeAbstract;
|
||||
|
||||
class Attribute extends NodeAbstract {
|
||||
/** @var Name Attribute name */
|
||||
public $name;
|
||||
public Name $name;
|
||||
|
||||
/** @var list<Arg> Attribute arguments */
|
||||
public $args;
|
||||
public array $args;
|
||||
|
||||
/**
|
||||
* @param Node\Name $name Attribute name
|
||||
* @param Node\Name $name Attribute name
|
||||
* @param list<Arg> $args Attribute arguments
|
||||
* @param array<string, mixed> $attributes Additional node attributes
|
||||
*/
|
||||
|
@ -6,7 +6,7 @@ use PhpParser\NodeAbstract;
|
||||
|
||||
class AttributeGroup extends NodeAbstract {
|
||||
/** @var Attribute[] Attributes */
|
||||
public $attrs;
|
||||
public array $attrs;
|
||||
|
||||
/**
|
||||
* @param Attribute[] $attrs PHP attributes
|
||||
|
@ -6,15 +6,15 @@ use PhpParser\NodeAbstract;
|
||||
|
||||
class ClosureUse extends NodeAbstract {
|
||||
/** @var Expr\Variable Variable to use */
|
||||
public $var;
|
||||
public Expr\Variable $var;
|
||||
/** @var bool Whether to use by reference */
|
||||
public $byRef;
|
||||
public bool $byRef;
|
||||
|
||||
/**
|
||||
* Constructs a closure use node.
|
||||
*
|
||||
* @param Expr\Variable $var Variable to use
|
||||
* @param bool $byRef Whether to use by reference
|
||||
* @param Expr\Variable $var Variable to use
|
||||
* @param bool $byRef Whether to use by reference
|
||||
* @param array<string, mixed> $attributes Additional attributes
|
||||
*/
|
||||
public function __construct(Expr\Variable $var, bool $byRef = false, array $attributes = []) {
|
||||
|
@ -6,18 +6,18 @@ use PhpParser\NodeAbstract;
|
||||
|
||||
class Const_ extends NodeAbstract {
|
||||
/** @var Identifier Name */
|
||||
public $name;
|
||||
public Identifier $name;
|
||||
/** @var Expr Value */
|
||||
public $value;
|
||||
public Expr $value;
|
||||
|
||||
/** @var Name|null Namespaced name (if using NameResolver) */
|
||||
public $namespacedName;
|
||||
public ?Name $namespacedName;
|
||||
|
||||
/**
|
||||
* Constructs a const node for use in class const and const statements.
|
||||
*
|
||||
* @param string|Identifier $name Name
|
||||
* @param Expr $value Value
|
||||
* @param string|Identifier $name Name
|
||||
* @param Expr $value Value
|
||||
* @param array<string, mixed> $attributes Additional attributes
|
||||
*/
|
||||
public function __construct($name, Expr $value, array $attributes = []) {
|
||||
|
@ -7,15 +7,15 @@ use PhpParser\NodeAbstract;
|
||||
|
||||
class DeclareItem extends NodeAbstract {
|
||||
/** @var Node\Identifier Key */
|
||||
public $key;
|
||||
public Identifier $key;
|
||||
/** @var Node\Expr Value */
|
||||
public $value;
|
||||
public Expr $value;
|
||||
|
||||
/**
|
||||
* Constructs a declare key=>value pair node.
|
||||
*
|
||||
* @param string|Node\Identifier $key Key
|
||||
* @param Node\Expr $value Value
|
||||
* @param string|Node\Identifier $key Key
|
||||
* @param Node\Expr $value Value
|
||||
* @param array<string, mixed> $attributes Additional attributes
|
||||
*/
|
||||
public function __construct($key, Node\Expr $value, array $attributes = []) {
|
||||
|
@ -6,15 +6,15 @@ use PhpParser\Node\Expr;
|
||||
|
||||
class ArrayDimFetch extends Expr {
|
||||
/** @var Expr Variable */
|
||||
public $var;
|
||||
public Expr $var;
|
||||
/** @var null|Expr Array index / dim */
|
||||
public $dim;
|
||||
public ?Expr $dim;
|
||||
|
||||
/**
|
||||
* Constructs an array index fetch node.
|
||||
*
|
||||
* @param Expr $var Variable
|
||||
* @param null|Expr $dim Array index / dim
|
||||
* @param Expr $var Variable
|
||||
* @param null|Expr $dim Array index / dim
|
||||
* @param array<string, mixed> $attributes Additional attributes
|
||||
*/
|
||||
public function __construct(Expr $var, ?Expr $dim = null, array $attributes = []) {
|
||||
|
@ -1,3 +1,11 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser\Node\Expr;
|
||||
|
||||
require __DIR__ . '/../ArrayItem.php';
|
||||
|
||||
if (false) {
|
||||
// For classmap-authoritative support.
|
||||
class ArrayItem extends \PhpParser\Node\ArrayItem {
|
||||
}
|
||||
}
|
||||
|
@ -11,12 +11,12 @@ class Array_ extends Expr {
|
||||
public const KIND_SHORT = 2; // [] syntax
|
||||
|
||||
/** @var ArrayItem[] Items */
|
||||
public $items;
|
||||
public array $items;
|
||||
|
||||
/**
|
||||
* Constructs an array node.
|
||||
*
|
||||
* @param ArrayItem[] $items Items of the array
|
||||
* @param ArrayItem[] $items Items of the array
|
||||
* @param array<string, mixed> $attributes Additional attributes
|
||||
*/
|
||||
public function __construct(array $items = [], array $attributes = []) {
|
||||
|
@ -7,22 +7,22 @@ use PhpParser\Node\Expr;
|
||||
use PhpParser\Node\FunctionLike;
|
||||
|
||||
class ArrowFunction extends Expr implements FunctionLike {
|
||||
/** @var bool */
|
||||
public $static;
|
||||
/** @var bool Whether the closure is static */
|
||||
public bool $static;
|
||||
|
||||
/** @var bool */
|
||||
public $byRef;
|
||||
/** @var bool Whether to return by reference */
|
||||
public bool $byRef;
|
||||
|
||||
/** @var Node\Param[] */
|
||||
public $params = [];
|
||||
public array $params = [];
|
||||
|
||||
/** @var null|Node\Identifier|Node\Name|Node\ComplexType */
|
||||
public $returnType;
|
||||
public ?Node $returnType;
|
||||
|
||||
/** @var Expr */
|
||||
public $expr;
|
||||
/** @var Expr Expression body */
|
||||
public Expr $expr;
|
||||
/** @var Node\AttributeGroup[] */
|
||||
public $attrGroups;
|
||||
public array $attrGroups;
|
||||
|
||||
/**
|
||||
* @param array{
|
||||
@ -30,7 +30,7 @@ class ArrowFunction extends Expr implements FunctionLike {
|
||||
* static?: bool,
|
||||
* byRef?: bool,
|
||||
* params?: Node\Param[],
|
||||
* returnType?: null|string|Node\Identifier|Node\Name|Node\ComplexType,
|
||||
* returnType?: null|Node\Identifier|Node\Name|Node\ComplexType,
|
||||
* attrGroups?: Node\AttributeGroup[]
|
||||
* } $subNodes Array of the following subnodes:
|
||||
* 'expr' : Expression body
|
||||
@ -46,8 +46,7 @@ class ArrowFunction extends Expr implements FunctionLike {
|
||||
$this->static = $subNodes['static'] ?? false;
|
||||
$this->byRef = $subNodes['byRef'] ?? false;
|
||||
$this->params = $subNodes['params'] ?? [];
|
||||
$returnType = $subNodes['returnType'] ?? null;
|
||||
$this->returnType = \is_string($returnType) ? new Node\Identifier($returnType) : $returnType;
|
||||
$this->returnType = $subNodes['returnType'] ?? null;
|
||||
$this->expr = $subNodes['expr'];
|
||||
$this->attrGroups = $subNodes['attrGroups'] ?? [];
|
||||
}
|
||||
|
@ -6,15 +6,15 @@ use PhpParser\Node\Expr;
|
||||
|
||||
class Assign extends Expr {
|
||||
/** @var Expr Variable */
|
||||
public $var;
|
||||
public Expr $var;
|
||||
/** @var Expr Expression */
|
||||
public $expr;
|
||||
public Expr $expr;
|
||||
|
||||
/**
|
||||
* Constructs an assignment node.
|
||||
*
|
||||
* @param Expr $var Variable
|
||||
* @param Expr $expr Expression
|
||||
* @param Expr $var Variable
|
||||
* @param Expr $expr Expression
|
||||
* @param array<string, mixed> $attributes Additional attributes
|
||||
*/
|
||||
public function __construct(Expr $var, Expr $expr, array $attributes = []) {
|
||||
|
@ -6,15 +6,15 @@ use PhpParser\Node\Expr;
|
||||
|
||||
abstract class AssignOp extends Expr {
|
||||
/** @var Expr Variable */
|
||||
public $var;
|
||||
public Expr $var;
|
||||
/** @var Expr Expression */
|
||||
public $expr;
|
||||
public Expr $expr;
|
||||
|
||||
/**
|
||||
* Constructs a compound assignment operation node.
|
||||
*
|
||||
* @param Expr $var Variable
|
||||
* @param Expr $expr Expression
|
||||
* @param Expr $var Variable
|
||||
* @param Expr $expr Expression
|
||||
* @param array<string, mixed> $attributes Additional attributes
|
||||
*/
|
||||
public function __construct(Expr $var, Expr $expr, array $attributes = []) {
|
||||
|
@ -6,15 +6,15 @@ use PhpParser\Node\Expr;
|
||||
|
||||
class AssignRef extends Expr {
|
||||
/** @var Expr Variable reference is assigned to */
|
||||
public $var;
|
||||
public Expr $var;
|
||||
/** @var Expr Variable which is referenced */
|
||||
public $expr;
|
||||
public Expr $expr;
|
||||
|
||||
/**
|
||||
* Constructs an assignment node.
|
||||
*
|
||||
* @param Expr $var Variable
|
||||
* @param Expr $expr Expression
|
||||
* @param Expr $var Variable
|
||||
* @param Expr $expr Expression
|
||||
* @param array<string, mixed> $attributes Additional attributes
|
||||
*/
|
||||
public function __construct(Expr $var, Expr $expr, array $attributes = []) {
|
||||
|
@ -6,15 +6,15 @@ use PhpParser\Node\Expr;
|
||||
|
||||
abstract class BinaryOp extends Expr {
|
||||
/** @var Expr The left hand side expression */
|
||||
public $left;
|
||||
public Expr $left;
|
||||
/** @var Expr The right hand side expression */
|
||||
public $right;
|
||||
public Expr $right;
|
||||
|
||||
/**
|
||||
* Constructs a binary operator node.
|
||||
*
|
||||
* @param Expr $left The left hand side expression
|
||||
* @param Expr $right The right hand side expression
|
||||
* @param Expr $left The left hand side expression
|
||||
* @param Expr $right The right hand side expression
|
||||
* @param array<string, mixed> $attributes Additional attributes
|
||||
*/
|
||||
public function __construct(Expr $left, Expr $right, array $attributes = []) {
|
||||
@ -32,8 +32,6 @@ abstract class BinaryOp extends Expr {
|
||||
*
|
||||
* In the case there are multiple possible sigils for an operator, this method does not
|
||||
* necessarily return the one used in the parsed code.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
abstract public function getOperatorSigil(): string;
|
||||
}
|
||||
|
@ -6,12 +6,12 @@ use PhpParser\Node\Expr;
|
||||
|
||||
class BitwiseNot extends Expr {
|
||||
/** @var Expr Expression */
|
||||
public $expr;
|
||||
public Expr $expr;
|
||||
|
||||
/**
|
||||
* Constructs a bitwise not node.
|
||||
*
|
||||
* @param Expr $expr Expression
|
||||
* @param Expr $expr Expression
|
||||
* @param array<string, mixed> $attributes Additional attributes
|
||||
*/
|
||||
public function __construct(Expr $expr, array $attributes = []) {
|
||||
|
@ -6,12 +6,12 @@ use PhpParser\Node\Expr;
|
||||
|
||||
class BooleanNot extends Expr {
|
||||
/** @var Expr Expression */
|
||||
public $expr;
|
||||
public Expr $expr;
|
||||
|
||||
/**
|
||||
* Constructs a boolean not node.
|
||||
*
|
||||
* @param Expr $expr Expression
|
||||
* @param Expr $expr Expression
|
||||
* @param array<string, mixed> $attributes Additional attributes
|
||||
*/
|
||||
public function __construct(Expr $expr, array $attributes = []) {
|
||||
|
@ -19,12 +19,8 @@ abstract class CallLike extends Expr {
|
||||
* Returns whether this call expression is actually a first class callable.
|
||||
*/
|
||||
public function isFirstClassCallable(): bool {
|
||||
foreach ($this->getRawArgs() as $arg) {
|
||||
if ($arg instanceof VariadicPlaceholder) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
$rawArgs = $this->getRawArgs();
|
||||
return count($rawArgs) === 1 && current($rawArgs) instanceof VariadicPlaceholder;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -6,12 +6,12 @@ use PhpParser\Node\Expr;
|
||||
|
||||
abstract class Cast extends Expr {
|
||||
/** @var Expr Expression */
|
||||
public $expr;
|
||||
public Expr $expr;
|
||||
|
||||
/**
|
||||
* Constructs a cast node.
|
||||
*
|
||||
* @param Expr $expr Expression
|
||||
* @param Expr $expr Expression
|
||||
* @param array<string, mixed> $attributes Additional attributes
|
||||
*/
|
||||
public function __construct(Expr $expr, array $attributes = []) {
|
||||
|
@ -2,15 +2,16 @@
|
||||
|
||||
namespace PhpParser\Node\Expr;
|
||||
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Expr;
|
||||
use PhpParser\Node\Identifier;
|
||||
use PhpParser\Node\Name;
|
||||
|
||||
class ClassConstFetch extends Expr {
|
||||
/** @var Name|Expr Class name */
|
||||
public $class;
|
||||
public Node $class;
|
||||
/** @var Identifier|Expr|Error Constant name */
|
||||
public $name;
|
||||
public Node $name;
|
||||
|
||||
/**
|
||||
* Constructs a class const fetch node.
|
||||
@ -19,7 +20,7 @@ class ClassConstFetch extends Expr {
|
||||
* @param string|Identifier|Expr|Error $name Constant name
|
||||
* @param array<string, mixed> $attributes Additional attributes
|
||||
*/
|
||||
public function __construct($class, $name, array $attributes = []) {
|
||||
public function __construct(Node $class, $name, array $attributes = []) {
|
||||
$this->attributes = $attributes;
|
||||
$this->class = $class;
|
||||
$this->name = \is_string($name) ? new Identifier($name) : $name;
|
||||
|
@ -6,12 +6,12 @@ use PhpParser\Node\Expr;
|
||||
|
||||
class Clone_ extends Expr {
|
||||
/** @var Expr Expression */
|
||||
public $expr;
|
||||
public Expr $expr;
|
||||
|
||||
/**
|
||||
* Constructs a clone node.
|
||||
*
|
||||
* @param Expr $expr Expression
|
||||
* @param Expr $expr Expression
|
||||
* @param array<string, mixed> $attributes Additional attributes
|
||||
*/
|
||||
public function __construct(Expr $expr, array $attributes = []) {
|
||||
|
@ -9,19 +9,19 @@ use PhpParser\Node\FunctionLike;
|
||||
|
||||
class Closure extends Expr implements FunctionLike {
|
||||
/** @var bool Whether the closure is static */
|
||||
public $static;
|
||||
public bool $static;
|
||||
/** @var bool Whether to return by reference */
|
||||
public $byRef;
|
||||
public bool $byRef;
|
||||
/** @var Node\Param[] Parameters */
|
||||
public $params;
|
||||
public array $params;
|
||||
/** @var ClosureUse[] use()s */
|
||||
public $uses;
|
||||
public array $uses;
|
||||
/** @var null|Node\Identifier|Node\Name|Node\ComplexType Return type */
|
||||
public $returnType;
|
||||
public ?Node $returnType;
|
||||
/** @var Node\Stmt[] Statements */
|
||||
public $stmts;
|
||||
public array $stmts;
|
||||
/** @var Node\AttributeGroup[] PHP attribute groups */
|
||||
public $attrGroups;
|
||||
public array $attrGroups;
|
||||
|
||||
/**
|
||||
* Constructs a lambda function node.
|
||||
@ -31,7 +31,7 @@ class Closure extends Expr implements FunctionLike {
|
||||
* byRef?: bool,
|
||||
* params?: Node\Param[],
|
||||
* uses?: ClosureUse[],
|
||||
* returnType?: null|string|Node\Identifier|Node\Name|Node\ComplexType,
|
||||
* returnType?: null|Node\Identifier|Node\Name|Node\ComplexType,
|
||||
* stmts?: Node\Stmt[],
|
||||
* attrGroups?: Node\AttributeGroup[],
|
||||
* } $subNodes Array of the following optional subnodes:
|
||||
@ -50,8 +50,7 @@ class Closure extends Expr implements FunctionLike {
|
||||
$this->byRef = $subNodes['byRef'] ?? false;
|
||||
$this->params = $subNodes['params'] ?? [];
|
||||
$this->uses = $subNodes['uses'] ?? [];
|
||||
$returnType = $subNodes['returnType'] ?? null;
|
||||
$this->returnType = \is_string($returnType) ? new Node\Identifier($returnType) : $returnType;
|
||||
$this->returnType = $subNodes['returnType'] ?? null;
|
||||
$this->stmts = $subNodes['stmts'] ?? [];
|
||||
$this->attrGroups = $subNodes['attrGroups'] ?? [];
|
||||
}
|
||||
|
@ -1,3 +1,11 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser\Node\Expr;
|
||||
|
||||
require __DIR__ . '/../ClosureUse.php';
|
||||
|
||||
if (false) {
|
||||
// For classmap-authoritative support.
|
||||
class ClosureUse extends \PhpParser\Node\ClosureUse {
|
||||
}
|
||||
}
|
||||
|
@ -7,12 +7,12 @@ use PhpParser\Node\Name;
|
||||
|
||||
class ConstFetch extends Expr {
|
||||
/** @var Name Constant name */
|
||||
public $name;
|
||||
public Name $name;
|
||||
|
||||
/**
|
||||
* Constructs a const fetch node.
|
||||
*
|
||||
* @param Name $name Constant name
|
||||
* @param Name $name Constant name
|
||||
* @param array<string, mixed> $attributes Additional attributes
|
||||
*/
|
||||
public function __construct(Name $name, array $attributes = []) {
|
||||
|
@ -6,12 +6,12 @@ use PhpParser\Node\Expr;
|
||||
|
||||
class Empty_ extends Expr {
|
||||
/** @var Expr Expression */
|
||||
public $expr;
|
||||
public Expr $expr;
|
||||
|
||||
/**
|
||||
* Constructs an empty() node.
|
||||
*
|
||||
* @param Expr $expr Expression
|
||||
* @param Expr $expr Expression
|
||||
* @param array<string, mixed> $attributes Additional attributes
|
||||
*/
|
||||
public function __construct(Expr $expr, array $attributes = []) {
|
||||
|
@ -6,12 +6,12 @@ use PhpParser\Node\Expr;
|
||||
|
||||
class ErrorSuppress extends Expr {
|
||||
/** @var Expr Expression */
|
||||
public $expr;
|
||||
public Expr $expr;
|
||||
|
||||
/**
|
||||
* Constructs an error suppress node.
|
||||
*
|
||||
* @param Expr $expr Expression
|
||||
* @param Expr $expr Expression
|
||||
* @param array<string, mixed> $attributes Additional attributes
|
||||
*/
|
||||
public function __construct(Expr $expr, array $attributes = []) {
|
||||
|
@ -6,12 +6,12 @@ use PhpParser\Node\Expr;
|
||||
|
||||
class Eval_ extends Expr {
|
||||
/** @var Expr Expression */
|
||||
public $expr;
|
||||
public Expr $expr;
|
||||
|
||||
/**
|
||||
* Constructs an eval() node.
|
||||
*
|
||||
* @param Expr $expr Expression
|
||||
* @param Expr $expr Expression
|
||||
* @param array<string, mixed> $attributes Additional attributes
|
||||
*/
|
||||
public function __construct(Expr $expr, array $attributes = []) {
|
||||
|
@ -10,12 +10,12 @@ class Exit_ extends Expr {
|
||||
public const KIND_DIE = 2;
|
||||
|
||||
/** @var null|Expr Expression */
|
||||
public $expr;
|
||||
public ?Expr $expr;
|
||||
|
||||
/**
|
||||
* Constructs an exit() node.
|
||||
*
|
||||
* @param null|Expr $expr Expression
|
||||
* @param null|Expr $expr Expression
|
||||
* @param array<string, mixed> $attributes Additional attributes
|
||||
*/
|
||||
public function __construct(?Expr $expr = null, array $attributes = []) {
|
||||
|
@ -7,18 +7,18 @@ use PhpParser\Node\Expr;
|
||||
|
||||
class FuncCall extends CallLike {
|
||||
/** @var Node\Name|Expr Function name */
|
||||
public $name;
|
||||
public Node $name;
|
||||
/** @var array<Node\Arg|Node\VariadicPlaceholder> Arguments */
|
||||
public $args;
|
||||
public array $args;
|
||||
|
||||
/**
|
||||
* Constructs a function call node.
|
||||
*
|
||||
* @param Node\Name|Expr $name Function name
|
||||
* @param array<Node\Arg|Node\VariadicPlaceholder> $args Arguments
|
||||
* @param Node\Name|Expr $name Function name
|
||||
* @param array<Node\Arg|Node\VariadicPlaceholder> $args Arguments
|
||||
* @param array<string, mixed> $attributes Additional attributes
|
||||
*/
|
||||
public function __construct($name, array $args = [], array $attributes = []) {
|
||||
public function __construct(Node $name, array $args = [], array $attributes = []) {
|
||||
$this->attributes = $attributes;
|
||||
$this->name = $name;
|
||||
$this->args = $args;
|
||||
|
@ -11,15 +11,15 @@ class Include_ extends Expr {
|
||||
public const TYPE_REQUIRE_ONCE = 4;
|
||||
|
||||
/** @var Expr Expression */
|
||||
public $expr;
|
||||
public Expr $expr;
|
||||
/** @var int Type of include */
|
||||
public $type;
|
||||
public int $type;
|
||||
|
||||
/**
|
||||
* Constructs an include node.
|
||||
*
|
||||
* @param Expr $expr Expression
|
||||
* @param int $type Type of include
|
||||
* @param Expr $expr Expression
|
||||
* @param int $type Type of include
|
||||
* @param array<string, mixed> $attributes Additional attributes
|
||||
*/
|
||||
public function __construct(Expr $expr, int $type, array $attributes = []) {
|
||||
|
@ -2,23 +2,24 @@
|
||||
|
||||
namespace PhpParser\Node\Expr;
|
||||
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Expr;
|
||||
use PhpParser\Node\Name;
|
||||
|
||||
class Instanceof_ extends Expr {
|
||||
/** @var Expr Expression */
|
||||
public $expr;
|
||||
public Expr $expr;
|
||||
/** @var Name|Expr Class name */
|
||||
public $class;
|
||||
public Node $class;
|
||||
|
||||
/**
|
||||
* Constructs an instanceof check node.
|
||||
*
|
||||
* @param Expr $expr Expression
|
||||
* @param Name|Expr $class Class name
|
||||
* @param Expr $expr Expression
|
||||
* @param Name|Expr $class Class name
|
||||
* @param array<string, mixed> $attributes Additional attributes
|
||||
*/
|
||||
public function __construct(Expr $expr, $class, array $attributes = []) {
|
||||
public function __construct(Expr $expr, Node $class, array $attributes = []) {
|
||||
$this->attributes = $attributes;
|
||||
$this->expr = $expr;
|
||||
$this->class = $class;
|
||||
|
@ -6,12 +6,12 @@ use PhpParser\Node\Expr;
|
||||
|
||||
class Isset_ extends Expr {
|
||||
/** @var Expr[] Variables */
|
||||
public $vars;
|
||||
public array $vars;
|
||||
|
||||
/**
|
||||
* Constructs an array node.
|
||||
*
|
||||
* @param Expr[] $vars Variables
|
||||
* @param Expr[] $vars Variables
|
||||
* @param array<string, mixed> $attributes Additional attributes
|
||||
*/
|
||||
public function __construct(array $vars, array $attributes = []) {
|
||||
|
@ -11,12 +11,12 @@ class List_ extends Expr {
|
||||
public const KIND_ARRAY = 2; // [] syntax
|
||||
|
||||
/** @var (ArrayItem|null)[] List of items to assign to */
|
||||
public $items;
|
||||
public array $items;
|
||||
|
||||
/**
|
||||
* Constructs a list() destructuring node.
|
||||
*
|
||||
* @param (ArrayItem|null)[] $items List of items to assign to
|
||||
* @param (ArrayItem|null)[] $items List of items to assign to
|
||||
* @param array<string, mixed> $attributes Additional attributes
|
||||
*/
|
||||
public function __construct(array $items, array $attributes = []) {
|
||||
|
@ -6,13 +6,15 @@ use PhpParser\Node;
|
||||
use PhpParser\Node\MatchArm;
|
||||
|
||||
class Match_ extends Node\Expr {
|
||||
/** @var Node\Expr */
|
||||
public $cond;
|
||||
/** @var Node\Expr Condition */
|
||||
public Node\Expr $cond;
|
||||
/** @var MatchArm[] */
|
||||
public $arms;
|
||||
public array $arms;
|
||||
|
||||
/**
|
||||
* @param Node\Expr $cond Condition
|
||||
* @param MatchArm[] $arms
|
||||
* @param array<string, mixed> $attributes Additional attributes
|
||||
*/
|
||||
public function __construct(Node\Expr $cond, array $arms = [], array $attributes = []) {
|
||||
$this->attributes = $attributes;
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
namespace PhpParser\Node\Expr;
|
||||
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Arg;
|
||||
use PhpParser\Node\Expr;
|
||||
use PhpParser\Node\Identifier;
|
||||
@ -9,18 +10,18 @@ use PhpParser\Node\VariadicPlaceholder;
|
||||
|
||||
class MethodCall extends CallLike {
|
||||
/** @var Expr Variable holding object */
|
||||
public $var;
|
||||
public Expr $var;
|
||||
/** @var Identifier|Expr Method name */
|
||||
public $name;
|
||||
public Node $name;
|
||||
/** @var array<Arg|VariadicPlaceholder> Arguments */
|
||||
public $args;
|
||||
public array $args;
|
||||
|
||||
/**
|
||||
* Constructs a function call node.
|
||||
*
|
||||
* @param Expr $var Variable holding object
|
||||
* @param string|Identifier|Expr $name Method name
|
||||
* @param array<Arg|VariadicPlaceholder> $args Arguments
|
||||
* @param Expr $var Variable holding object
|
||||
* @param string|Identifier|Expr $name Method name
|
||||
* @param array<Arg|VariadicPlaceholder> $args Arguments
|
||||
* @param array<string, mixed> $attributes Additional attributes
|
||||
*/
|
||||
public function __construct(Expr $var, $name, array $args = [], array $attributes = []) {
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user