mirror of
https://github.com/nikic/PHP-Parser.git
synced 2025-07-25 16:21:23 +02:00
Compare commits
123 Commits
v5.0.0beta
...
master
Author | SHA1 | Date | |
---|---|---|---|
|
c724dde741 | ||
|
7c4f7ca987 | ||
|
c5216ac220 | ||
|
507fa7632e | ||
|
3e74153456 | ||
|
b815a165bd | ||
|
c1f6c4c8d8 | ||
|
66d5018bb7 | ||
|
dc47765670 | ||
|
8ab65b4adc | ||
|
7fc3bcf970 | ||
|
acf8f5ef01 | ||
|
ae59794362 | ||
|
4964f8d5dd | ||
|
20b0d55f66 | ||
|
c9d0b6c9f2 | ||
|
14f9c9df7f | ||
|
fa02db3f30 | ||
|
d4fce83c2c | ||
|
7d3039c378 | ||
|
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 |
.gitattributesNodeAbstract.phpNodeDumper.phpNodeFinder.phpNodeTraverser.phpphpstan-baseline.neonphpunit.xml.dist
.github/workflows
.php-cs-fixer.dist.phpCHANGELOG.mdCONTRIBUTING.mdMakefileREADME.mdUPGRADE-5.0.mdcomposer.jsondoc
grammar
lib/PhpParser
Builder
BuilderFactory.phpBuilderHelpers.phpComment.phpConstExprEvaluator.phpError.phpInternal
Lexer.phpLexer
Emulative.php
Modifiers.phpNameContext.phpNode.phpTokenEmulator
Node
Expr
Identifier.phpMatchArm.phpName.phpParam.phpPropertyHook.phpPropertyItem.phpScalar
Stmt
Block.phpClassLike.phpConst_.phpDeclareDeclare.phpProperty.phpPropertyProperty.phpStaticVar.phpThrow_.phpUseUse.php
UseItem.phpNodeVisitor
CommentAnnotatingVisitor.phpFindingVisitor.phpNameResolver.phpNodeConnectingVisitor.phpParentConnectingVisitor.php
Parser
ParserAbstract.phpParserFactory.phpPhpVersion.phpPrettyPrinter
PrettyPrinterAbstract.phpcompatibility_tokens.phptest
PhpParser
Builder
ClassConstTest.phpClassTest.phpEnumCaseTest.phpEnumTest.phpFunctionTest.phpInterfaceTest.phpMethodTest.phpNamespaceTest.phpParamTest.phpPropertyTest.phpTraitTest.phpTraitUseAdaptationTest.phpTraitUseTest.phpUseTest.php
BuilderFactoryTest.phpBuilderHelpersTest.phpCodeParsingTest.phpCodeTestAbstract.phpCodeTestParser.phpCommentTest.phpCompatibilityTest.phpConstExprEvaluatorTest.phpErrorHandler
ErrorTest.phpInternal
JsonDecoderTest.phpLexer
LexerTest.phpModifiersTest.phpNameContextTest.phpNode
NodeAbstractTest.phpNodeDumperTest.phpNodeFinderTest.phpNodeTraverserTest.phpNodeVisitor
FindingVisitorTest.phpFirstFindingVisitorTest.phpNameResolverTest.phpNodeConnectingVisitorTest.phpParentConnectingVisitorTest.php
Parser
ParserFactoryTest.phpParserTestAbstract.phpPhpVersionTest.phpPrettyPrinterTest.phpTokenTest.phpcode
formatPreservation
basic.testcomments.testconstants.testgroup_use.testindent.testlistInsertion.testlistRemoval.testproperty_hooks.test
parser
blockComments.testcommentAtEndOfClass.testcomments.testemptyFile.test
errorHandling
expr
alternative_array_syntax.testarrayDef.testarrow_function.testassign.testclosure.testconstant_expr.testexit.testexprInIsset.testexprInList.test
formattingAttributes.testnopPositions.testfetchAndCall
firstClassCallables.testlogic.testmatch.testmath.testnew.testnewDeref.testpipe.testternaryAndCoalesce.testuvs
scalar
constantString.testdocString.testflexibleDocString.testflexibleDocStringErrors.testfloat.testmagicConst.testnumberSeparators.testunicodeEscape.test
semiReserved.teststmt
attributes.test
class
anonymous.testasymmetric_visibility.testimplicitPublic.testmodifier_error.testname.testphp4Style.testpropertyTypes.testproperty_hooks.testproperty_modifiers.testproperty_promotion.testreadonlyAsClassName.testsimple.test
const.testcontrolFlow.testdeclare.testfunction
builtinTypeDeclarations.testbyRef.testclone_function.testdefaultValues.testdisjointNormalFormTypes.testexit_die_function.testintersectionTypes.testnullableTypes.testparameters_trailing_comma.testtypeDeclarations.testtypeVersions.testunionTypes.testvariadic.testvariadicDefaultValue.test
generator
haltCompilerOffset.testnamespace
newInInitializer.testvoidCast.testprettyPrinter
fixtures
test_old
tools
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
|
||||
|
39
.github/workflows/main.yml
vendored
39
.github/workflows/main.yml
vendored
@@ -10,7 +10,7 @@ jobs:
|
||||
name: "PHP 7.4 Unit Tests (with coverage)"
|
||||
steps:
|
||||
- name: "Checkout"
|
||||
uses: "actions/checkout@v3"
|
||||
uses: "actions/checkout@v4"
|
||||
- name: "Install PHP"
|
||||
uses: "shivammathur/setup-php@v2"
|
||||
with:
|
||||
@@ -20,7 +20,7 @@ jobs:
|
||||
- name: "Install dependencies"
|
||||
run: |
|
||||
composer require php-coveralls/php-coveralls:^2.2 --dev --no-update
|
||||
composer update --no-progress --prefer-dist
|
||||
COMPOSER_ROOT_VERSION=dev-master composer update --no-progress --prefer-dist
|
||||
- name: "Tests"
|
||||
run: "php vendor/bin/phpunit --coverage-clover build/logs/clover.xml"
|
||||
- name: Coveralls
|
||||
@@ -38,62 +38,67 @@ jobs:
|
||||
- "8.1"
|
||||
- "8.2"
|
||||
- "8.3"
|
||||
- "8.4"
|
||||
fail-fast: false
|
||||
steps:
|
||||
- name: "Checkout"
|
||||
uses: "actions/checkout@v3"
|
||||
uses: "actions/checkout@v4"
|
||||
- name: "Install PHP"
|
||||
uses: "shivammathur/setup-php@v2"
|
||||
with:
|
||||
coverage: "none"
|
||||
php-version: "${{ matrix.php-version }}"
|
||||
ini-file: "development"
|
||||
tools: composer:v2
|
||||
- name: "Install dependencies"
|
||||
run: "composer update --no-progress --prefer-dist ${{ matrix.flags }}"
|
||||
run: "COMPOSER_ROOT_VERSION=dev-master composer update --no-progress --prefer-dist ${{ matrix.flags }}"
|
||||
- name: "PHPUnit"
|
||||
run: "php vendor/bin/phpunit"
|
||||
test_old_73_80:
|
||||
runs-on: "ubuntu-latest"
|
||||
name: "PHP 7.4 Code on PHP 8.2 Integration Tests"
|
||||
name: "PHP 7.4 Code on PHP 8.4 Integration Tests"
|
||||
steps:
|
||||
- name: "Checkout"
|
||||
uses: "actions/checkout@v3"
|
||||
uses: "actions/checkout@v4"
|
||||
- name: "Install PHP"
|
||||
uses: "shivammathur/setup-php@v2"
|
||||
with:
|
||||
coverage: "none"
|
||||
php-version: "8.2"
|
||||
php-version: "8.4"
|
||||
ini-file: "development"
|
||||
tools: composer:v2
|
||||
- name: "Install PHP 8 dependencies"
|
||||
run: "composer update --no-progress --prefer-dist"
|
||||
run: "COMPOSER_ROOT_VERSION=dev-master composer update --no-progress --prefer-dist"
|
||||
- name: "Tests"
|
||||
run: "test_old/run-php-src.sh 7.4.33"
|
||||
test_old_80_70:
|
||||
runs-on: "ubuntu-latest"
|
||||
name: "PHP 8.3 Code on PHP 7.4 Integration Tests"
|
||||
name: "PHP 8.4 Code on PHP 7.4 Integration Tests"
|
||||
steps:
|
||||
- name: "Checkout"
|
||||
uses: "actions/checkout@v3"
|
||||
uses: "actions/checkout@v4"
|
||||
- name: "Install PHP"
|
||||
uses: "shivammathur/setup-php@v2"
|
||||
with:
|
||||
coverage: "none"
|
||||
php-version: "7.4"
|
||||
ini-file: "development"
|
||||
tools: composer:v2
|
||||
- name: "Install PHP 8 dependencies"
|
||||
run: "composer update --no-progress --prefer-dist"
|
||||
run: "COMPOSER_ROOT_VERSION=dev-master composer update --no-progress --prefer-dist"
|
||||
- name: "Tests"
|
||||
run: "test_old/run-php-src.sh 8.3.0RC2"
|
||||
run: "test_old/run-php-src.sh 8.4.0beta5"
|
||||
phpstan:
|
||||
runs-on: "ubuntu-latest"
|
||||
name: "PHPStan"
|
||||
steps:
|
||||
- name: "Checkout"
|
||||
uses: "actions/checkout@v3"
|
||||
uses: "actions/checkout@v4"
|
||||
- name: "Install PHP"
|
||||
uses: "shivammathur/setup-php@v2"
|
||||
with:
|
||||
coverage: "none"
|
||||
php-version: "8.2"
|
||||
php-version: "8.3"
|
||||
tools: composer:v2
|
||||
- name: "Install dependencies"
|
||||
run: |
|
||||
@@ -105,15 +110,15 @@ jobs:
|
||||
name: "PHP-CS-Fixer"
|
||||
steps:
|
||||
- name: "Checkout"
|
||||
uses: "actions/checkout@v3"
|
||||
uses: "actions/checkout@v4"
|
||||
- name: "Install PHP"
|
||||
uses: "shivammathur/setup-php@v2"
|
||||
with:
|
||||
coverage: "none"
|
||||
php-version: "8.2"
|
||||
php-version: "8.3"
|
||||
tools: composer:v2
|
||||
- name: "Install dependencies"
|
||||
run: |
|
||||
cd tools && composer install
|
||||
- name: "php-cs-fixer"
|
||||
run: "php tools/vendor/bin/php-cs-fixer fix"
|
||||
run: "php tools/vendor/bin/php-cs-fixer fix --dry-run"
|
||||
|
@@ -21,11 +21,6 @@ return $config->setRiskyAllowed(true)
|
||||
'declare_strict_types' => true,
|
||||
// Keep argument formatting for now.
|
||||
'method_argument_space' => ['on_multiline' => 'ignore'],
|
||||
'binary_operator_spaces' => [
|
||||
'default' => 'at_least_single_space',
|
||||
// Work around https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues/7303.
|
||||
'operators' => ['=' => null],
|
||||
],
|
||||
'phpdoc_align' => ['align' => 'left'],
|
||||
'phpdoc_trim' => true,
|
||||
'no_empty_phpdoc' => true,
|
||||
|
160
CHANGELOG.md
160
CHANGELOG.md
@@ -1,3 +1,163 @@
|
||||
Version 5.5.0 (2025-05-31)
|
||||
--------------------------
|
||||
|
||||
### Added
|
||||
|
||||
* [8.5] Added support for attributes on constants. `Stmt\Const_` now has an `attrGroups` subnode.
|
||||
* Added `weakReferences` option to `NodeConnectingVisitor` and `ParentConnectingVisitor`. This
|
||||
will create the parent/next/prev references as WeakReferences, to avoid making the AST cyclic
|
||||
and thus increasing GC pressure.
|
||||
|
||||
### Changed
|
||||
|
||||
* Attributes on parameters are now printed on separate lines if the pretty printer target version
|
||||
is PHP 7.4 or older (which is the default). This allows them to be interpreted as comments,
|
||||
instead of causing a parse error. Specify a target version of PHP 8.0 or newer to restore the
|
||||
previous behavior.
|
||||
|
||||
Version 5.4.0 (2024-12-30)
|
||||
--------------------------
|
||||
|
||||
### Added
|
||||
|
||||
* Added `Property::isAbstract()` and `Property::isFinal()` methods.
|
||||
* Added `PropertyHook::isFinal()` method.
|
||||
* Emit an error if property hook is used on declaration with multiple properties.
|
||||
|
||||
### Fixed
|
||||
|
||||
* Make legacy class aliases compatible with classmap-authoritative autoloader.
|
||||
* `Param::isPromoted()` and `Param::isPublic()` now returns true for parameters that have property
|
||||
hooks but no explicit visibility modifier.
|
||||
* `PropertyHook::getStmts()` now correctly desugars short `set` hooks. `set => $value` will be
|
||||
expanded to `set { $this->propertyName = $value; }`. This requires the `propertyName` attribute
|
||||
on the hook to be set, which is now also set by the parser. If the attribute is not set,
|
||||
`getStmts()` will throw an error for short set hooks, as it is not possible to produce a correct
|
||||
desugaring.
|
||||
|
||||
Version 5.3.1 (2024-10-08)
|
||||
--------------------------
|
||||
|
||||
### Added
|
||||
|
||||
* Added support for declaring functions with name `exit` or `die`, to allow their use in stubs.
|
||||
|
||||
Version 5.3.0 (2024-09-29)
|
||||
--------------------------
|
||||
|
||||
### Added
|
||||
|
||||
* Added `indent` option to pretty printer, which can be used to specify the indentation to use
|
||||
(defaulting to four spaces). This also allows using tab indentation.
|
||||
|
||||
### Fixed
|
||||
|
||||
* Resolve names in `PropertyHook`s in the `NameResolver`.
|
||||
* Include the trailing semicolon inside `Stmt\GroupUse` nodes, making them consistent with
|
||||
`Stmt\Use_` nodes.
|
||||
* Fixed indentation sometimes becoming negative in formatting-preserving pretty printer, resulting
|
||||
in `ValueError`s.
|
||||
|
||||
Version 5.2.0 (2024-09-15)
|
||||
--------------------------
|
||||
|
||||
### Added
|
||||
|
||||
* [8.4] Added support for `__PROPERTY__` magic constant, represented using a
|
||||
`Node\Scalar\MagicConst\Property` node.
|
||||
* [8.4] Added support for property hooks, which are represented using a new `hooks` subnode on
|
||||
`Node\Stmt\Property` and `Node\Param`, which contains an array of `Node\PropertyHook`.
|
||||
* [8.4] Added support for asymmetric visibility modifiers. Property `flags` can now hold the
|
||||
additional bits `Modifiers::PUBLIC_SET`, `Modifiers::PROTECTED_SET` and `Modifiers::PRIVATE_SET`.
|
||||
* [8.4] Added support for generalized exit function. For backwards compatibility, exit without
|
||||
argument or a single plain argument continues to use a `Node\Expr\Exit_` node. Otherwise (e.g.
|
||||
if a named argument is used) it will be represented as a plain `Node\Expr\FuncCall`.
|
||||
* Added support for passing enum values to various builder methods, like `BuilderFactory::val()`.
|
||||
|
||||
### Removed
|
||||
|
||||
* Removed support for alternative array syntax `$array{0}` from the PHP 8 parser. It is still
|
||||
supported by the PHP 7 parser. This is necessary in order to support property hooks.
|
||||
|
||||
Version 5.1.0 (2024-07-01)
|
||||
--------------------------
|
||||
|
||||
### Added
|
||||
|
||||
* [8.4] Added support for dereferencing `new` expressions without parentheses.
|
||||
|
||||
### Fixed
|
||||
|
||||
* Fixed redundant parentheses being added when pretty printing ternary expressions.
|
||||
|
||||
### Changed
|
||||
|
||||
* Made some phpdoc types more precise.
|
||||
|
||||
Version 5.0.2 (2024-03-05)
|
||||
--------------------------
|
||||
|
||||
### Fixed
|
||||
|
||||
* Fix handling of indentation on next line after opening PHP tag in formatting-preserving pretty
|
||||
printer.
|
||||
|
||||
### Changed
|
||||
|
||||
* Avoid cyclic references in `Parser` objects. This means that no longer used parser objects are
|
||||
immediately destroyed now, instead of requiring cycle GC.
|
||||
* Update `PhpVersion::getNewestSupported()` to report PHP 8.3 instead of PHP 8.2.
|
||||
|
||||
Version 5.0.1 (2024-02-21)
|
||||
--------------------------
|
||||
|
||||
### Changed
|
||||
|
||||
* Added check to detect use of PHP-Parser with libraries that define `T_*` compatibility tokens
|
||||
with incorrect type (such as string instead of int). This would lead to `TypeError`s down the
|
||||
line. Now an `Error` will be thrown early to indicate the problem.
|
||||
|
||||
Version 5.0.0 (2024-01-07)
|
||||
--------------------------
|
||||
|
||||
See UPGRADE-5.0 for detailed migration instructions.
|
||||
|
||||
### Fixed
|
||||
|
||||
* Fixed parent class of `PropertyItem` and `UseItem`.
|
||||
|
||||
Version 5.0.0-rc1 (2023-12-20)
|
||||
------------------------------
|
||||
|
||||
See UPGRADE-5.0 for detailed migration instructions.
|
||||
|
||||
### Fixed
|
||||
|
||||
* Fixed parsing of empty files.
|
||||
|
||||
### Added
|
||||
|
||||
* Added support for printing additional attributes (like `kind`) in `NodeDumper`.
|
||||
* Added `rawValue` attribute to `InterpolatedStringPart` and heredoc/nowdoc `String_`s, which
|
||||
provides the original, unparsed value. It was previously only available for non-interpolated
|
||||
single/double quoted strings.
|
||||
* Added `Stmt\Block` to represent `{}` code blocks. Previously, such code blocks were flattened
|
||||
into the parent statements array. `Stmt\Block` will not be created for structures that are
|
||||
typically used with code blocks, for example `if ($x) { $y; }` will be represented as previously,
|
||||
while `if ($x) { { $x; } }` will have an extra `Stmt\Block` wrapper.
|
||||
|
||||
### Changed
|
||||
|
||||
* Use visitor to assign comments. This fixes the long-standing issue where comments were assigned
|
||||
to all nodes sharing a starting position. Now only the outer-most node will hold the comments.
|
||||
* Don't parse unicode escape sequences when targeting PHP < 7.0.
|
||||
* Improve NodeDumper performance for large dumps.
|
||||
|
||||
### Removed
|
||||
|
||||
* Removed `Stmt\Throw_` node, use `Expr\Throw_` inside `Stmt\Expression` instead.
|
||||
* Removed `ParserFactory::create()`.
|
||||
|
||||
Version 5.0.0-beta1 (2023-09-17)
|
||||
--------------------------------
|
||||
|
||||
|
@@ -2,3 +2,31 @@
|
||||
|
||||
This project uses PSR-12 with consistent brace placement. This means that the opening brace is
|
||||
always on the same line, even for class and method declarations.
|
||||
|
||||
## Tools
|
||||
|
||||
This project uses PHP-CS-Fixer and PHPStan. You can invoke them using `make`:
|
||||
|
||||
```shell
|
||||
make php-cs-fixer
|
||||
make phpstan
|
||||
```
|
||||
|
||||
## Adding support for new PHP syntax
|
||||
|
||||
1. If necessary, add emulation support for new tokens.
|
||||
* Add a new subclass of `Lexer\TokenEmulator`. Take inspiration from existing classes.
|
||||
* Add the new class to the array in `Lexer\Emulative`.
|
||||
* Add tests for the emulation in `Lexer\EmulativeTest`. You'll want to modify
|
||||
`provideTestReplaceKeywords()` for new reserved keywords and `provideTestLexNewFeatures()` for
|
||||
other emulations.
|
||||
2. Add any new node classes that are needed.
|
||||
3. Add support for the new syntax in `grammar/php.y`. Regenerate the parser by running
|
||||
`php grammar/rebuildParsers.php`. Use `--debug` if there are conflicts.
|
||||
4. Add pretty-printing support by implementing a `pFooBar()` method in `PrettyPrinter\Standard`.
|
||||
5. Add tests both in `test/code/parser` and `test/code/prettyPrinter`.
|
||||
6. Add support for formatting-preserving pretty-printing. This is done by modifying the data tables
|
||||
at the end of `PrettyPrinterAbstract`. Add a test in `test/code/formatPreservation`.
|
||||
7. Does the new syntax feature namespaced names? If so, add support for name resolution in
|
||||
`NodeVisitor\NameResolver`. Test it in `NodeVisitor\NameResolverTest`.
|
||||
8. Does the new syntax require any changes to builders? Is so, make them :)
|
||||
|
7
Makefile
7
Makefile
@@ -4,7 +4,10 @@ tools/vendor:
|
||||
composer install -d tools
|
||||
|
||||
phpstan: tools/vendor
|
||||
tools/vendor/bin/phpstan
|
||||
php tools/vendor/bin/phpstan
|
||||
|
||||
php-cs-fixer: tools/vendor
|
||||
tools/vendor/bin/php-cs-fixer fix
|
||||
php tools/vendor/bin/php-cs-fixer fix
|
||||
|
||||
tests:
|
||||
php vendor/bin/phpunit
|
11
README.md
11
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.4; for parsing PHP 7.0 to PHP 8.3, with limited support for parsing PHP 5.x).
|
||||
[**Documentation for version 5.x**][doc_master] (current; for running on PHP >= 7.4; for parsing PHP 7.0 to PHP 8.4, with limited support for parsing PHP 5.x).
|
||||
|
||||
[**Documentation for version 4.x**][doc_4_x] (stable; for running on PHP >= 7.0; for parsing PHP 5.2 to PHP 8.3).
|
||||
|
||||
[Documentation for version 3.x][doc_3_x] (unsupported; for running on PHP >= 5.5; for parsing PHP 5.2 to PHP 7.2).
|
||||
[Documentation for version 4.x][doc_4_x] (supported; for running on PHP >= 7.0; for parsing PHP 5.2 to PHP 8.3).
|
||||
|
||||
Features
|
||||
--------
|
||||
@@ -213,9 +211,8 @@ Component documentation:
|
||||
* [AST builders](doc/component/AST_builders.markdown)
|
||||
* Fluent builders for AST nodes
|
||||
* [Lexer](doc/component/Lexer.markdown)
|
||||
* Lexer options
|
||||
* Token and file positions for nodes
|
||||
* Custom attributes
|
||||
* Emulation
|
||||
* Tokens, positions and attributes
|
||||
* [Error handling](doc/component/Error_handling.markdown)
|
||||
* Column information for errors
|
||||
* Error recovery (parsing of syntactically incorrect code)
|
||||
|
207
UPGRADE-5.0.md
207
UPGRADE-5.0.md
@@ -13,6 +13,7 @@ In particular, if an older `PhpVersion` is specified, then:
|
||||
|
||||
* For versions before PHP 7.0, `$foo =& new Bar()` assignments are allowed without error.
|
||||
* For versions before PHP 7.0, invalid octal literals `089` are allowed without error.
|
||||
* For versions before PHP 7.0, unicode escape sequences `\u{123}` in strings are not parsed.
|
||||
* Type hints are interpreted as a class `Name` or as a built-in `Identifier` depending on PHP
|
||||
version, for example `int` is treated as a class name on PHP 5.6 and as a built-in on PHP 7.0.
|
||||
|
||||
@@ -27,16 +28,17 @@ The following symbols are affected by this removal:
|
||||
* The `PhpParser\Parser\Php5` class has been removed.
|
||||
* The `PhpParser\Parser\Multiple` class has been removed. While not strictly related to PHP 5 support, this functionality is no longer useful without it.
|
||||
* The `PhpParser\ParserFactory::ONLY_PHP5` and `PREFER_PHP5` options have been removed.
|
||||
* The `PhpParser\ParserFactory::PREFER_PHP7` option is now equivalent to `ONLY_PHP7`.
|
||||
|
||||
### Changes to the parser factory
|
||||
|
||||
The `ParserFactory::create()` method is deprecated in favor of three new methods that provide more fine-grained control over the PHP version being targeted:
|
||||
The `ParserFactory::create()` method has been removed in favor of three new methods that provide more fine-grained control over the PHP version being targeted:
|
||||
|
||||
* `createForNewestSupportedVersion()`: Use this if you don't know the PHP version of the code you're parsing. It's better to assume a too new version than a too old one.
|
||||
* `createForHostVersion()`: Use this if you're parsing code for the PHP version you're running on.
|
||||
* `createForVersion()`: Use this if you know the PHP version of the code you want to parse.
|
||||
|
||||
The `createForNewestSupportedVersion()` and `createForHostVersion()` are available since PHP-Parser 4.18.0, to allow libraries to support PHP-Parser 4 and 5 at the same time more easily.
|
||||
|
||||
In all cases, the PHP version is a fairly weak hint that is only used on a best-effort basis. The parser will usually accept code for newer versions if it does not have any backwards-compatibility implications.
|
||||
|
||||
For example, if you specify version `"8.0"`, then `class ReadOnly {}` is treated as a valid class declaration, while using `public readonly int $prop` will lead to a parse error. However, `final public const X = Y;` will be accepted in both cases.
|
||||
@@ -61,6 +63,35 @@ $parser = $factory->create(ParserFactory::ONLY_PHP5);
|
||||
$parser = $factory->createForVersion(PhpVersion::fromString("5.6"));
|
||||
```
|
||||
|
||||
### Changes to the throw representation
|
||||
|
||||
Previously, `throw` statements like `throw $e;` were represented using the `Stmt\Throw_` class,
|
||||
while uses inside other expressions (such as `$x ?? throw $e`) used the `Expr\Throw_` class.
|
||||
|
||||
Now, `throw $e;` is represented as a `Stmt\Expression` that contains an `Expr\Throw_`. The
|
||||
`Stmt\Throw_` class has been removed.
|
||||
|
||||
```php
|
||||
# Code
|
||||
throw $e;
|
||||
|
||||
# Before
|
||||
Stmt_Throw(
|
||||
expr: Expr_Variable(
|
||||
name: e
|
||||
)
|
||||
)
|
||||
|
||||
# After
|
||||
Stmt_Expression(
|
||||
expr: Expr_Throw(
|
||||
expr: Expr_Variable(
|
||||
name: e
|
||||
)
|
||||
)
|
||||
)
|
||||
```
|
||||
|
||||
### Changes to the array destructuring representation
|
||||
|
||||
Previously, the `list($x) = $y` destructuring syntax was represented using a `Node\Expr\List_`
|
||||
@@ -71,6 +102,49 @@ Now, destructuring is always represented using `Node\Expr\List_`. The `kind` att
|
||||
`Node\Expr\List_::KIND_LIST` or `Node\Expr\List_::KIND_ARRAY` specifies which syntax was actually
|
||||
used.
|
||||
|
||||
```php
|
||||
# Code
|
||||
[$x] = $y;
|
||||
|
||||
# Before
|
||||
Expr_Assign(
|
||||
var: Expr_Array(
|
||||
items: array(
|
||||
0: Expr_ArrayItem(
|
||||
key: null
|
||||
value: Expr_Variable(
|
||||
name: x
|
||||
)
|
||||
byRef: false
|
||||
unpack: false
|
||||
)
|
||||
)
|
||||
)
|
||||
expr: Expr_Variable(
|
||||
name: y
|
||||
)
|
||||
)
|
||||
|
||||
# After
|
||||
Expr_Assign(
|
||||
var: Expr_List(
|
||||
items: array(
|
||||
0: ArrayItem(
|
||||
key: null
|
||||
value: Expr_Variable(
|
||||
name: x
|
||||
)
|
||||
byRef: false
|
||||
unpack: false
|
||||
)
|
||||
)
|
||||
)
|
||||
expr: Expr_Variable(
|
||||
name: y
|
||||
)
|
||||
)
|
||||
```
|
||||
|
||||
### Changes to the name representation
|
||||
|
||||
Previously, `Name` nodes had a `parts` subnode, which stores an array of name parts, split by
|
||||
@@ -82,6 +156,123 @@ now represented by `Name(name: 'Foo\Bar')` instead.
|
||||
It is possible to convert the name to the previous representation using `$name->getParts()`. The
|
||||
`Name` constructor continues to accept both the string and the array representation.
|
||||
|
||||
The `Name::getParts()` method is available since PHP-Parser 4.16.0, to allow libraries to support
|
||||
PHP-Parser 4 and 5 at the same time more easily.
|
||||
|
||||
### Changes to the block representation
|
||||
|
||||
Previously, code blocks `{ ... }` were always flattened into their parent statement list. For
|
||||
example `while ($x) { $a; { $b; } $c; }` would produce the same node structure as
|
||||
`if ($x) { $a; $b; $c; }`, namely a `Stmt\While_` node whose `stmts` subnode is an array of three
|
||||
statements.
|
||||
|
||||
Now, the nested `{ $b; }` block is represented using an explicit `Stmt\Block` node. However, the
|
||||
outer `{ $a; { $b; } $c; }` block is still represented using a simple array in the `stmts` subnode.
|
||||
|
||||
```php
|
||||
# Code
|
||||
while ($x) { $a; { $b; } $c; }
|
||||
|
||||
# Before
|
||||
Stmt_While(
|
||||
cond: Expr_Variable(
|
||||
name: x
|
||||
)
|
||||
stmts: array(
|
||||
0: Stmt_Expression(
|
||||
expr: Expr_Variable(
|
||||
name: a
|
||||
)
|
||||
)
|
||||
1: Stmt_Expression(
|
||||
expr: Expr_Variable(
|
||||
name: b
|
||||
)
|
||||
)
|
||||
2: Stmt_Expression(
|
||||
expr: Expr_Variable(
|
||||
name: c
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
# After
|
||||
Stmt_While(
|
||||
cond: Expr_Variable(
|
||||
name: x
|
||||
)
|
||||
stmts: array(
|
||||
0: Stmt_Expression(
|
||||
expr: Expr_Variable(
|
||||
name: a
|
||||
)
|
||||
)
|
||||
1: Stmt_Block(
|
||||
stmts: array(
|
||||
0: Stmt_Expression(
|
||||
expr: Expr_Variable(
|
||||
name: b
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
2: Stmt_Expression(
|
||||
expr: Expr_Variable(
|
||||
name: c
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
```
|
||||
|
||||
### Changes to comment assignment
|
||||
|
||||
Previously, comments were assigned to all nodes starting at the same position. Now they will be
|
||||
assigned to the outermost node only.
|
||||
|
||||
```php
|
||||
# Code
|
||||
// Comment
|
||||
$a + $b;
|
||||
|
||||
# Before
|
||||
Stmt_Expression(
|
||||
expr: Expr_BinaryOp_Plus(
|
||||
left: Expr_Variable(
|
||||
name: a
|
||||
comments: array(
|
||||
0: // Comment
|
||||
)
|
||||
)
|
||||
right: Expr_Variable(
|
||||
name: b
|
||||
)
|
||||
comments: array(
|
||||
0: // Comment
|
||||
)
|
||||
)
|
||||
comments: array(
|
||||
0: // Comment
|
||||
)
|
||||
)
|
||||
|
||||
# After
|
||||
Stmt_Expression(
|
||||
expr: Expr_BinaryOp_Plus(
|
||||
left: Expr_Variable(
|
||||
name: a
|
||||
)
|
||||
right: Expr_Variable(
|
||||
name: b
|
||||
)
|
||||
)
|
||||
comments: array(
|
||||
0: // Comment
|
||||
)
|
||||
)
|
||||
```
|
||||
|
||||
### Renamed nodes
|
||||
|
||||
A number of AST nodes have been renamed or moved in the AST hierarchy:
|
||||
@@ -104,7 +295,7 @@ The old class names have been retained as aliases for backwards compatibility. H
|
||||
|
||||
Modifier flags (as used by the `$flags` subnode of `Class_`, `ClassMethod`, `Property`, etc.) are now available as class constants on a separate `PhpParser\Modifiers` class, instead of being part of `PhpParser\Node\Stmt\Class_`, to make it clearer that these are used by many different nodes. The old constants are deprecated, but are still available.
|
||||
|
||||
```
|
||||
```php
|
||||
PhpParser\Node\Stmt\Class_::MODIFIER_PUBLIC -> PhpParser\Modifiers::PUBLIC
|
||||
PhpParser\Node\Stmt\Class_::MODIFIER_PROTECTED -> PhpParser\Modifiers::PROTECTED
|
||||
PhpParser\Node\Stmt\Class_::MODIFIER_PRIVATE -> PhpParser\Modifiers::PRIVATE
|
||||
@@ -117,7 +308,7 @@ PhpParser\Node\Stmt\Class_::VISIBILITY_MODIFIER_MASK -> PhpParser\Modifiers::VIS
|
||||
|
||||
### Changes to node constructors
|
||||
|
||||
Node constructor arguments accepting types now longer accept plain strings. Either an `Identifier` or `Name` (or `ComplexType`) should be passed instead. This affects the following constructor arguments:
|
||||
Node constructor arguments accepting types no longer accept plain strings. Either an `Identifier` or `Name` (or `ComplexType`) should be passed instead. This affects the following constructor arguments:
|
||||
|
||||
* The `'returnType'` key of `$subNodes` argument of `Node\Expr\ArrowFunction`.
|
||||
* The `'returnType'` key of `$subNodes` argument of `Node\Expr\Closure`.
|
||||
@@ -202,7 +393,7 @@ The pretty printer now accepts a `phpVersion` option, which accepts a `PhpVersio
|
||||
|
||||
* For PHP >= 7.0 (default), short array syntax `[]` will be used by default. This does not affect nodes that specify an explicit array syntax using the `kind` attribute.
|
||||
* For PHP >= 7.0 (default), parentheses around `yield` expressions will only be printed when necessary. Previously, parentheses were always printed, even if `yield` was used as a statement.
|
||||
* For PHP >= 7.1 (default), the short array syntax `[]` will be used for destructuring by default (instead of `list()`). This does not affect nodes that specify and explicit syntax using the `kind` attribute.
|
||||
* For PHP >= 7.1 (default), the short array syntax `[]` will be used for destructuring by default (instead of `list()`). This does not affect nodes that specify an explicit syntax using the `kind` attribute.
|
||||
* For PHP >= 7.3 (default), a newline is no longer forced after heredoc/nowdoc strings, as the requirement for this has been removed with the introduction of flexible heredoc/nowdoc strings.
|
||||
* For PHP >= 7.3 (default), heredoc/nowdoc strings are now indented just like regular code. This was allowed with the introduction of flexible heredoc/nowdoc strings.
|
||||
|
||||
@@ -220,7 +411,7 @@ protected function p(
|
||||
|
||||
The `$precedence` is the precedence of the direct parent operator (if any), while `$lhsPrecedence` is that precedence of the nearest binary operator on whose left-hand-side the node occurs. For unary operators, only the `$lhsPrecedence` is relevant.
|
||||
|
||||
Recursive calls in pretty-printer methods should generally continue calling `p()` without additional parameters. However, pretty-printer methods for operators that participate in precedence resolution need to be adjusted. For example, typical implementations for operators looks as follows now:
|
||||
Recursive calls in pretty-printer methods should generally continue calling `p()` without additional parameters. However, pretty-printer methods for operators that participate in precedence resolution need to be adjusted. For example, typical implementations for operators look as follows now:
|
||||
|
||||
```php
|
||||
protected function pExpr_BinaryOp_Plus(
|
||||
@@ -301,7 +492,7 @@ Additionally, the token array is now terminated by a sentinel token with ID 0.
|
||||
|
||||
The lexer API is reduced to a single `Lexer::tokenize()` method, which returns an array of tokens. The `startLexing()` and `getNextToken()` methods have been removed.
|
||||
|
||||
Responsibility for determining start and end attributes for nodes has been moved from the lexer to the parser. The lexer no longer accepts an options array. The `usedAttributes` option has been removed without replacement, and the parser will now unconditionally add the `comments`, `startLine`, `endLine`, `startFilePos`, `startEndPos`, `startTokenPos` and `startEndPos` attributes.
|
||||
Responsibility for determining start and end attributes for nodes has been moved from the lexer to the parser. The lexer no longer accepts an options array. The `usedAttributes` option has been removed without replacement, and the parser will now unconditionally add the `comments`, `startLine`, `endLine`, `startFilePos`, `endFilePos`, `startTokenPos` and `endTokenPos` attributes.
|
||||
|
||||
There should no longer be a need to directly interact with the `Lexer` for end users, as the `ParserFactory` will create an appropriate instance, and no additional configuration of the lexer is necessary. To use formatting-preserving pretty printing, the setup boilerplate changes as follows:
|
||||
|
||||
@@ -328,7 +519,7 @@ $newStmts = $traverser->traverse($oldStmts);
|
||||
|
||||
$parser = (new ParserFactory())->createForNewestSupportedVersion();
|
||||
$oldStmts = $parser->parse($code);
|
||||
$oldTokens = $lexer->getTokens();
|
||||
$oldTokens = $parser->getTokens();
|
||||
|
||||
$traverser = new NodeTraverser(new NodeVisitor\CloningVisitor());
|
||||
$newStmts = $traverser->traverse($oldStmts);
|
||||
|
@@ -19,7 +19,7 @@
|
||||
"ext-ctype": "*"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^7.0 || ^8.0 || ^9.0",
|
||||
"phpunit/phpunit": "^9.0",
|
||||
"ircmaxell/php-yacc": "^0.0.7"
|
||||
},
|
||||
"extra": {
|
||||
|
@@ -43,7 +43,7 @@ following caveats:
|
||||
|
||||
As the parser is based on the tokens returned by `token_get_all` (which is only able to lex the PHP
|
||||
version it runs on), additionally a wrapper for emulating tokens from newer versions is provided.
|
||||
This allows to parse PHP 8.3 source code running on PHP 7.4, for example. This emulation is not
|
||||
This allows to parse PHP 8.4 source code running on PHP 7.4, for example. This emulation is not
|
||||
perfect, but works well in practice.
|
||||
|
||||
Finally, it should be noted that the parser aims to accept all valid code, not reject all invalid
|
||||
|
@@ -27,9 +27,8 @@ Component documentation
|
||||
* [AST builders](component/AST_builders.markdown)
|
||||
* Fluent builders for AST nodes
|
||||
* [Lexer](component/Lexer.markdown)
|
||||
* Lexer options
|
||||
* Token and file positions for nodes
|
||||
* Custom attributes
|
||||
* Emulation
|
||||
* Tokens, positions and attributes
|
||||
* [Error handling](component/Error_handling.markdown)
|
||||
* Column information for errors
|
||||
* Error recovery (parsing of syntactically incorrect code)
|
||||
|
@@ -51,3 +51,17 @@ obtained through `$node->getAttribute('next')`.
|
||||
|
||||
`ParentConnectingVisitor` and `NodeConnectingVisitor` should not be used at the same time. The latter
|
||||
includes the functionality of the former.
|
||||
|
||||
|
||||
How can I limit the impact of cyclic references in the AST?
|
||||
-----
|
||||
|
||||
NodeConnectingVisitor adds a parent reference, which introduces a cycle. This means that the AST can now only be collected by the cycle garbage collector.
|
||||
This in turn can lead to performance and/or memory issues.
|
||||
|
||||
To break the cyclic references between AST nodes `NodeConnectingVisitor` supports a boolean `$weakReferences` constructor parameter.
|
||||
When set to `true`, all attributes added by `NodeConnectingVisitor` will be wrapped into a `WeakReference` object.
|
||||
|
||||
After enabling this parameter, the parent node can be obtained through `$node->getAttribute('weak_parent')`,
|
||||
the previous node can be obtained through `$node->getAttribute('weak_previous')`, and the next node can be
|
||||
obtained through `$node->getAttribute('weak_next')`.
|
@@ -104,7 +104,7 @@ This will result in the following output (which includes attributes):
|
||||
"nodeType": "Scalar_String",
|
||||
"attributes": {
|
||||
"startLine": 5,
|
||||
"endLine": 5
|
||||
"endLine": 5,
|
||||
"kind": 2,
|
||||
"rawValue": "\"\\n\""
|
||||
},
|
||||
|
@@ -37,6 +37,7 @@ integer should be printed as decimal, hexadecimal, etc). Additionally, it suppor
|
||||
* `phpVersion` (defaults to 7.4) allows opting into formatting that is not supported by older PHP
|
||||
versions.
|
||||
* `newline` (defaults to `"\n"`) can be set to `"\r\n"` in order to produce Windows newlines.
|
||||
* `indent` (defaults to four spaces `" "`) can be set to any number of spaces or a single tab.
|
||||
* `shortArraySyntax` determines the used array syntax if the `kind` attribute is not set. This is
|
||||
a legacy option, and `phpVersion` should be used to control this behavior instead.
|
||||
|
||||
@@ -97,5 +98,5 @@ If you make use of the name resolution functionality, you will likely want to di
|
||||
the AST and causing spurious changes to the pretty printed code. For more information, see the
|
||||
[name resolution documentation](Name_resolution.markdown).
|
||||
|
||||
The formatting-preservation works on a best-effort basis and may sometimes reformat more code tha
|
||||
The formatting-preservation works on a best-effort basis and may sometimes reformat more code than
|
||||
necessary. If you encounter problems while using this functionality, please open an issue.
|
||||
|
@@ -1,7 +1,7 @@
|
||||
<?php declare(strict_types=1);
|
||||
$meta #
|
||||
#semval($) $this->semValue
|
||||
#semval($,%t) $this->semValue
|
||||
#semval($) $self->semValue
|
||||
#semval($,%t) $self->semValue
|
||||
#semval(%n) $stackPos-(%l-%n)
|
||||
#semval(%n,%t) $stackPos-(%l-%n)
|
||||
|
||||
@@ -28,68 +28,68 @@ class #(-p) extends \PhpParser\ParserAbstract
|
||||
public const %s = %n;
|
||||
#endtokenval
|
||||
|
||||
protected $tokenToSymbolMapSize = #(YYMAXLEX);
|
||||
protected $actionTableSize = #(YYLAST);
|
||||
protected $gotoTableSize = #(YYGLAST);
|
||||
protected int $tokenToSymbolMapSize = #(YYMAXLEX);
|
||||
protected int $actionTableSize = #(YYLAST);
|
||||
protected int $gotoTableSize = #(YYGLAST);
|
||||
|
||||
protected $invalidSymbol = #(YYBADCH);
|
||||
protected $errorSymbol = #(YYINTERRTOK);
|
||||
protected $defaultAction = #(YYDEFAULT);
|
||||
protected $unexpectedTokenRule = #(YYUNEXPECTED);
|
||||
protected int $invalidSymbol = #(YYBADCH);
|
||||
protected int $errorSymbol = #(YYINTERRTOK);
|
||||
protected int $defaultAction = #(YYDEFAULT);
|
||||
protected int $unexpectedTokenRule = #(YYUNEXPECTED);
|
||||
|
||||
protected $YY2TBLSTATE = #(YY2TBLSTATE);
|
||||
protected $numNonLeafStates = #(YYNLSTATES);
|
||||
protected int $YY2TBLSTATE = #(YY2TBLSTATE);
|
||||
protected int $numNonLeafStates = #(YYNLSTATES);
|
||||
|
||||
protected $symbolToName = array(
|
||||
protected array $symbolToName = array(
|
||||
#listvar terminals
|
||||
);
|
||||
|
||||
protected $tokenToSymbol = array(
|
||||
protected array $tokenToSymbol = array(
|
||||
#listvar yytranslate
|
||||
);
|
||||
|
||||
protected $action = array(
|
||||
protected array $action = array(
|
||||
#listvar yyaction
|
||||
);
|
||||
|
||||
protected $actionCheck = array(
|
||||
protected array $actionCheck = array(
|
||||
#listvar yycheck
|
||||
);
|
||||
|
||||
protected $actionBase = array(
|
||||
protected array $actionBase = array(
|
||||
#listvar yybase
|
||||
);
|
||||
|
||||
protected $actionDefault = array(
|
||||
protected array $actionDefault = array(
|
||||
#listvar yydefault
|
||||
);
|
||||
|
||||
protected $goto = array(
|
||||
protected array $goto = array(
|
||||
#listvar yygoto
|
||||
);
|
||||
|
||||
protected $gotoCheck = array(
|
||||
protected array $gotoCheck = array(
|
||||
#listvar yygcheck
|
||||
);
|
||||
|
||||
protected $gotoBase = array(
|
||||
protected array $gotoBase = array(
|
||||
#listvar yygbase
|
||||
);
|
||||
|
||||
protected $gotoDefault = array(
|
||||
protected array $gotoDefault = array(
|
||||
#listvar yygdefault
|
||||
);
|
||||
|
||||
protected $ruleToNonTerminal = array(
|
||||
protected array $ruleToNonTerminal = array(
|
||||
#listvar yylhs
|
||||
);
|
||||
|
||||
protected $ruleToLength = array(
|
||||
protected array $ruleToLength = array(
|
||||
#listvar yylen
|
||||
);
|
||||
#if -t
|
||||
|
||||
protected $productions = array(
|
||||
protected array $productions = array(
|
||||
#production-strings;
|
||||
);
|
||||
#endif
|
||||
@@ -97,7 +97,7 @@ class #(-p) extends \PhpParser\ParserAbstract
|
||||
protected function initReduceCallbacks(): void {
|
||||
$this->reduceCallbacks = [
|
||||
#reduce
|
||||
%n => function ($stackPos) {
|
||||
%n => static function ($self, $stackPos) {
|
||||
%b
|
||||
},
|
||||
#noact
|
||||
|
246
grammar/php.y
246
grammar/php.y
@@ -1,6 +1,7 @@
|
||||
%pure_parser
|
||||
%expect 2
|
||||
|
||||
%right T_VOID_CAST
|
||||
%right T_THROW
|
||||
%left T_INCLUDE T_INCLUDE_ONCE T_EVAL T_REQUIRE T_REQUIRE_ONCE
|
||||
%left ','
|
||||
@@ -26,6 +27,7 @@
|
||||
%left '+' '-' '.'
|
||||
#endif
|
||||
#if PHP8
|
||||
%left T_PIPE
|
||||
%left '.'
|
||||
%left T_SL T_SR
|
||||
%left '+' '-'
|
||||
@@ -81,7 +83,10 @@
|
||||
%token T_USE
|
||||
%token T_INSTEADOF
|
||||
%token T_GLOBAL
|
||||
%right T_STATIC T_ABSTRACT T_FINAL T_PRIVATE T_PROTECTED T_PUBLIC T_READONLY
|
||||
%token T_STATIC T_ABSTRACT T_FINAL T_PRIVATE T_PROTECTED T_PUBLIC T_READONLY
|
||||
%token T_PUBLIC_SET
|
||||
%token T_PROTECTED_SET
|
||||
%token T_PRIVATE_SET
|
||||
%token T_VAR
|
||||
%token T_UNSET
|
||||
%token T_ISSET
|
||||
@@ -103,6 +108,7 @@
|
||||
%token T_TRAIT_C
|
||||
%token T_METHOD_C
|
||||
%token T_FUNC_C
|
||||
%token T_PROPERTY_C
|
||||
%token T_LINE
|
||||
%token T_FILE
|
||||
%token T_START_HEREDOC
|
||||
@@ -252,8 +258,11 @@ top_statement:
|
||||
$this->checkNamespace($$); }
|
||||
| T_USE use_declarations semi { $$ = Stmt\Use_[$2, Stmt\Use_::TYPE_NORMAL]; }
|
||||
| T_USE use_type use_declarations semi { $$ = Stmt\Use_[$3, $2]; }
|
||||
| group_use_declaration semi
|
||||
| T_CONST constant_declaration_list semi { $$ = Stmt\Const_[$2]; }
|
||||
| group_use_declaration
|
||||
| T_CONST constant_declaration_list semi { $$ = new Stmt\Const_($2, attributes(), []); }
|
||||
| attributes T_CONST constant_declaration_list semi
|
||||
{ $$ = new Stmt\Const_($3, attributes(), $1);
|
||||
$this->checkConstantAttributes($$); }
|
||||
;
|
||||
|
||||
use_type:
|
||||
@@ -262,9 +271,9 @@ use_type:
|
||||
;
|
||||
|
||||
group_use_declaration:
|
||||
T_USE use_type legacy_namespace_name T_NS_SEPARATOR '{' unprefixed_use_declarations '}'
|
||||
T_USE use_type legacy_namespace_name T_NS_SEPARATOR '{' unprefixed_use_declarations '}' semi
|
||||
{ $$ = Stmt\GroupUse[$3, $6, $2]; }
|
||||
| T_USE legacy_namespace_name T_NS_SEPARATOR '{' inline_use_declarations '}'
|
||||
| T_USE legacy_namespace_name T_NS_SEPARATOR '{' inline_use_declarations '}' semi
|
||||
{ $$ = Stmt\GroupUse[$2, $5, Stmt\Use_::TYPE_UNKNOWN]; }
|
||||
;
|
||||
|
||||
@@ -366,21 +375,13 @@ inner_statement:
|
||||
;
|
||||
|
||||
non_empty_statement:
|
||||
'{' inner_statement_list '}'
|
||||
{
|
||||
if ($2) {
|
||||
$$ = $2; prependLeadingComments($$);
|
||||
} else {
|
||||
makeNop($$);
|
||||
if (null === $$) { $$ = array(); }
|
||||
}
|
||||
}
|
||||
| T_IF '(' expr ')' statement elseif_list else_single
|
||||
{ $$ = Stmt\If_[$3, ['stmts' => toArray($5), 'elseifs' => $6, 'else' => $7]]; }
|
||||
'{' inner_statement_list '}' { $$ = Stmt\Block[$2]; }
|
||||
| T_IF '(' expr ')' blocklike_statement elseif_list else_single
|
||||
{ $$ = Stmt\If_[$3, ['stmts' => $5, 'elseifs' => $6, 'else' => $7]]; }
|
||||
| T_IF '(' expr ')' ':' inner_statement_list new_elseif_list new_else_single T_ENDIF ';'
|
||||
{ $$ = Stmt\If_[$3, ['stmts' => $6, 'elseifs' => $7, 'else' => $8]]; }
|
||||
| T_WHILE '(' expr ')' while_statement { $$ = Stmt\While_[$3, $5]; }
|
||||
| T_DO statement T_WHILE '(' expr ')' ';' { $$ = Stmt\Do_ [$5, toArray($2)]; }
|
||||
| T_DO blocklike_statement T_WHILE '(' expr ')' ';' { $$ = Stmt\Do_ [$5, $2]; }
|
||||
| T_FOR '(' for_expr ';' for_expr ';' for_expr ')' for_statement
|
||||
{ $$ = Stmt\For_[['init' => $3, 'cond' => $5, 'loop' => $7, 'stmts' => $9]]; }
|
||||
| T_SWITCH '(' expr ')' switch_case_list { $$ = Stmt\Switch_[$3, $5]; }
|
||||
@@ -394,16 +395,7 @@ non_empty_statement:
|
||||
$$ = Stmt\InlineHTML[$1];
|
||||
$$->setAttribute('hasLeadingNewline', $this->inlineHtmlHasLeadingNewline(#1));
|
||||
}
|
||||
| expr semi {
|
||||
$e = $1;
|
||||
if ($e instanceof Expr\Throw_) {
|
||||
// For backwards-compatibility reasons, convert throw in statement position into
|
||||
// Stmt\Throw_ rather than Stmt\Expression(Expr\Throw_).
|
||||
$$ = Stmt\Throw_[$e->expr];
|
||||
} else {
|
||||
$$ = Stmt\Expression[$e];
|
||||
}
|
||||
}
|
||||
| expr semi { $$ = Stmt\Expression[$1]; }
|
||||
| T_UNSET '(' variables_list ')' semi { $$ = Stmt\Unset_[$3]; }
|
||||
| T_FOREACH '(' expr T_AS foreach_variable ')' foreach_statement
|
||||
{ $$ = Stmt\Foreach_[$3, $5[0], ['keyVar' => null, 'byRef' => $5[1], 'stmts' => $7]]; }
|
||||
@@ -416,14 +408,16 @@ non_empty_statement:
|
||||
{ $$ = Stmt\TryCatch[$3, $5, $6]; $this->checkTryCatch($$); }
|
||||
| T_GOTO identifier_not_reserved semi { $$ = Stmt\Goto_[$2]; }
|
||||
| identifier_not_reserved ':' { $$ = Stmt\Label[$1]; }
|
||||
| error { $$ = array(); /* means: no statement */ }
|
||||
| error { $$ = null; /* means: no statement */ }
|
||||
;
|
||||
|
||||
statement:
|
||||
non_empty_statement
|
||||
| ';'
|
||||
{ makeNop($$);
|
||||
if ($$ === null) $$ = array(); /* means: no statement */ }
|
||||
| ';' { makeNop($$); }
|
||||
;
|
||||
|
||||
blocklike_statement:
|
||||
statement { toBlock($1); }
|
||||
;
|
||||
|
||||
catches:
|
||||
@@ -475,15 +469,17 @@ block_or_error:
|
||||
| error { $$ = []; }
|
||||
;
|
||||
|
||||
identifier_maybe_readonly:
|
||||
fn_identifier:
|
||||
identifier_not_reserved
|
||||
| T_READONLY { $$ = Node\Identifier[$1]; }
|
||||
| T_EXIT { $$ = Node\Identifier[$1]; }
|
||||
| T_CLONE { $$ = Node\Identifier[$1]; }
|
||||
;
|
||||
|
||||
function_declaration_statement:
|
||||
T_FUNCTION optional_ref identifier_maybe_readonly '(' parameter_list ')' optional_return_type block_or_error
|
||||
T_FUNCTION optional_ref fn_identifier '(' parameter_list ')' optional_return_type block_or_error
|
||||
{ $$ = Stmt\Function_[$3, ['byRef' => $2, 'params' => $5, 'returnType' => $7, 'stmts' => $8, 'attrGroups' => []]]; }
|
||||
| attributes T_FUNCTION optional_ref identifier_maybe_readonly '(' parameter_list ')' optional_return_type block_or_error
|
||||
| attributes T_FUNCTION optional_ref fn_identifier '(' parameter_list ')' optional_return_type block_or_error
|
||||
{ $$ = Stmt\Function_[$4, ['byRef' => $3, 'params' => $6, 'returnType' => $8, 'stmts' => $9, 'attrGroups' => $1]]; }
|
||||
;
|
||||
|
||||
@@ -554,17 +550,17 @@ non_empty_class_name_list:
|
||||
;
|
||||
|
||||
for_statement:
|
||||
statement { $$ = toArray($1); }
|
||||
blocklike_statement
|
||||
| ':' inner_statement_list T_ENDFOR ';' { $$ = $2; }
|
||||
;
|
||||
|
||||
foreach_statement:
|
||||
statement { $$ = toArray($1); }
|
||||
blocklike_statement
|
||||
| ':' inner_statement_list T_ENDFOREACH ';' { $$ = $2; }
|
||||
;
|
||||
|
||||
declare_statement:
|
||||
non_empty_statement { $$ = toArray($1); }
|
||||
non_empty_statement { toBlock($1); }
|
||||
| ';' { $$ = null; }
|
||||
| ':' inner_statement_list T_ENDDECLARE ';' { $$ = $2; }
|
||||
;
|
||||
@@ -624,7 +620,7 @@ match_arm:
|
||||
;
|
||||
|
||||
while_statement:
|
||||
statement { $$ = toArray($1); }
|
||||
blocklike_statement { $$ = $1; }
|
||||
| ':' inner_statement_list T_ENDWHILE ';' { $$ = $2; }
|
||||
;
|
||||
|
||||
@@ -634,7 +630,7 @@ elseif_list:
|
||||
;
|
||||
|
||||
elseif:
|
||||
T_ELSEIF '(' expr ')' statement { $$ = Stmt\ElseIf_[$3, toArray($5)]; }
|
||||
T_ELSEIF '(' expr ')' blocklike_statement { $$ = Stmt\ElseIf_[$3, $5]; }
|
||||
;
|
||||
|
||||
new_elseif_list:
|
||||
@@ -649,7 +645,7 @@ new_elseif:
|
||||
|
||||
else_single:
|
||||
/* empty */ { $$ = null; }
|
||||
| T_ELSE statement { $$ = Stmt\Else_[toArray($2)]; }
|
||||
| T_ELSE blocklike_statement { $$ = Stmt\Else_[$2]; }
|
||||
;
|
||||
|
||||
new_else_single:
|
||||
@@ -686,18 +682,24 @@ property_modifier:
|
||||
T_PUBLIC { $$ = Modifiers::PUBLIC; }
|
||||
| T_PROTECTED { $$ = Modifiers::PROTECTED; }
|
||||
| T_PRIVATE { $$ = Modifiers::PRIVATE; }
|
||||
| T_PUBLIC_SET { $$ = Modifiers::PUBLIC_SET; }
|
||||
| T_PROTECTED_SET { $$ = Modifiers::PROTECTED_SET; }
|
||||
| T_PRIVATE_SET { $$ = Modifiers::PRIVATE_SET; }
|
||||
| T_READONLY { $$ = Modifiers::READONLY; }
|
||||
| T_FINAL { $$ = Modifiers::FINAL; }
|
||||
;
|
||||
|
||||
parameter:
|
||||
optional_attributes optional_property_modifiers optional_type_without_static
|
||||
optional_arg_ref optional_ellipsis plain_variable
|
||||
{ $$ = new Node\Param($6, null, $3, $4, $5, attributes(), $2, $1);
|
||||
$this->checkParam($$); }
|
||||
optional_arg_ref optional_ellipsis plain_variable optional_property_hook_list
|
||||
{ $$ = new Node\Param($6, null, $3, $4, $5, attributes(), $2, $1, $7);
|
||||
$this->checkParam($$);
|
||||
$this->addPropertyNameToHooks($$); }
|
||||
| optional_attributes optional_property_modifiers optional_type_without_static
|
||||
optional_arg_ref optional_ellipsis plain_variable '=' expr
|
||||
{ $$ = new Node\Param($6, $8, $3, $4, $5, attributes(), $2, $1);
|
||||
$this->checkParam($$); }
|
||||
optional_arg_ref optional_ellipsis plain_variable '=' expr optional_property_hook_list
|
||||
{ $$ = new Node\Param($6, $8, $3, $4, $5, attributes(), $2, $1, $9);
|
||||
$this->checkParam($$);
|
||||
$this->addPropertyNameToHooks($$); }
|
||||
| optional_attributes optional_property_modifiers optional_type_without_static
|
||||
optional_arg_ref optional_ellipsis error
|
||||
{ $$ = new Node\Param(Expr\Error[], null, $3, $4, $5, attributes(), $2, $1); }
|
||||
@@ -786,6 +788,22 @@ argument_list:
|
||||
| '(' variadic_placeholder ')' { init($2); }
|
||||
;
|
||||
|
||||
clone_argument_list:
|
||||
'(' ')' { $$ = array(); }
|
||||
| '(' non_empty_clone_argument_list optional_comma ')' { $$ = $2; }
|
||||
| '(' expr ',' ')' { init(Node\Arg[$2, false, false]); }
|
||||
| '(' variadic_placeholder ')' { init($2); }
|
||||
;
|
||||
|
||||
non_empty_clone_argument_list:
|
||||
expr ',' argument
|
||||
{ init(new Node\Arg($1, false, false, stackAttributes(#1)), $3); }
|
||||
| argument_no_expr
|
||||
{ init($1); }
|
||||
| non_empty_clone_argument_list ',' argument
|
||||
{ push($1, $3); }
|
||||
;
|
||||
|
||||
variadic_placeholder:
|
||||
T_ELLIPSIS { $$ = Node\VariadicPlaceholder[]; }
|
||||
;
|
||||
@@ -795,14 +813,18 @@ non_empty_argument_list:
|
||||
| non_empty_argument_list ',' argument { push($1, $3); }
|
||||
;
|
||||
|
||||
argument:
|
||||
expr { $$ = Node\Arg[$1, false, false]; }
|
||||
| ampersand variable { $$ = Node\Arg[$2, true, false]; }
|
||||
argument_no_expr:
|
||||
ampersand variable { $$ = Node\Arg[$2, true, false]; }
|
||||
| T_ELLIPSIS expr { $$ = Node\Arg[$2, false, true]; }
|
||||
| identifier_maybe_reserved ':' expr
|
||||
{ $$ = new Node\Arg($3, false, false, attributes(), $1); }
|
||||
;
|
||||
|
||||
argument:
|
||||
expr { $$ = Node\Arg[$1, false, false]; }
|
||||
| argument_no_expr { $$ = $1; }
|
||||
;
|
||||
|
||||
global_var_list:
|
||||
non_empty_global_var_list no_comma
|
||||
;
|
||||
@@ -831,7 +853,7 @@ static_var:
|
||||
;
|
||||
|
||||
class_statement_list_ex:
|
||||
class_statement_list_ex class_statement { if ($2 !== null) { push($1, $2); } }
|
||||
class_statement_list_ex class_statement { if ($2 !== null) { push($1, $2); } else { $$ = $1; } }
|
||||
| /* empty */ { init(); }
|
||||
;
|
||||
|
||||
@@ -843,8 +865,14 @@ class_statement_list:
|
||||
|
||||
class_statement:
|
||||
optional_attributes variable_modifiers optional_type_without_static property_declaration_list semi
|
||||
{ $$ = new Stmt\Property($2, $4, attributes(), $3, $1);
|
||||
$this->checkProperty($$, #2); }
|
||||
{ $$ = new Stmt\Property($2, $4, attributes(), $3, $1); }
|
||||
#if PHP8
|
||||
| optional_attributes variable_modifiers optional_type_without_static property_declaration_list '{' property_hook_list '}'
|
||||
{ $$ = new Stmt\Property($2, $4, attributes(), $3, $1, $6);
|
||||
$this->checkPropertyHooksForMultiProperty($$, #5);
|
||||
$this->checkEmptyPropertyHookList($6, #5);
|
||||
$this->addPropertyNameToHooks($$); }
|
||||
#endif
|
||||
| optional_attributes method_modifiers T_CONST class_const_list semi
|
||||
{ $$ = new Stmt\ClassConst($4, $2, attributes(), $1);
|
||||
$this->checkClassConst($$, #2); }
|
||||
@@ -916,6 +944,9 @@ member_modifier:
|
||||
T_PUBLIC { $$ = Modifiers::PUBLIC; }
|
||||
| T_PROTECTED { $$ = Modifiers::PROTECTED; }
|
||||
| T_PRIVATE { $$ = Modifiers::PRIVATE; }
|
||||
| T_PUBLIC_SET { $$ = Modifiers::PUBLIC_SET; }
|
||||
| T_PROTECTED_SET { $$ = Modifiers::PROTECTED_SET; }
|
||||
| T_PRIVATE_SET { $$ = Modifiers::PRIVATE_SET; }
|
||||
| T_STATIC { $$ = Modifiers::STATIC; }
|
||||
| T_ABSTRACT { $$ = Modifiers::ABSTRACT; }
|
||||
| T_FINAL { $$ = Modifiers::FINAL; }
|
||||
@@ -941,6 +972,39 @@ property_declaration:
|
||||
| property_decl_name '=' expr { $$ = Node\PropertyItem[$1, $3]; }
|
||||
;
|
||||
|
||||
property_hook_list:
|
||||
/* empty */ { $$ = []; }
|
||||
| property_hook_list property_hook { push($1, $2); }
|
||||
;
|
||||
|
||||
optional_property_hook_list:
|
||||
/* empty */ { $$ = []; }
|
||||
#if PHP8
|
||||
| '{' property_hook_list '}' { $$ = $2; $this->checkEmptyPropertyHookList($2, #1); }
|
||||
#endif
|
||||
;
|
||||
|
||||
property_hook:
|
||||
optional_attributes property_hook_modifiers optional_ref identifier_not_reserved property_hook_body
|
||||
{ $$ = Node\PropertyHook[$4, $5, ['flags' => $2, 'byRef' => $3, 'params' => [], 'attrGroups' => $1]];
|
||||
$this->checkPropertyHook($$, null); }
|
||||
| optional_attributes property_hook_modifiers optional_ref identifier_not_reserved '(' parameter_list ')' property_hook_body
|
||||
{ $$ = Node\PropertyHook[$4, $8, ['flags' => $2, 'byRef' => $3, 'params' => $6, 'attrGroups' => $1]];
|
||||
$this->checkPropertyHook($$, #5); }
|
||||
;
|
||||
|
||||
property_hook_body:
|
||||
';' { $$ = null; }
|
||||
| '{' inner_statement_list '}' { $$ = $2; }
|
||||
| T_DOUBLE_ARROW expr ';' { $$ = $2; }
|
||||
;
|
||||
|
||||
property_hook_modifiers:
|
||||
/* empty */ { $$ = 0; }
|
||||
| property_hook_modifiers member_modifier
|
||||
{ $this->checkPropertyHookModifiers($1, $2, #2); $$ = $1 | $2; }
|
||||
;
|
||||
|
||||
expr_list_forbid_comma:
|
||||
non_empty_expr_list no_comma
|
||||
;
|
||||
@@ -974,6 +1038,7 @@ expr:
|
||||
}
|
||||
| new_expr
|
||||
| match
|
||||
| T_CLONE clone_argument_list { $$ = Expr\FuncCall[new Node\Name($1, stackAttributes(#1)), $2]; }
|
||||
| T_CLONE expr { $$ = Expr\Clone_[$2]; }
|
||||
| variable T_PLUS_EQUAL expr { $$ = Expr\AssignOp\Plus [$1, $3]; }
|
||||
| variable T_MINUS_EQUAL expr { $$ = Expr\AssignOp\Minus [$1, $3]; }
|
||||
@@ -1023,6 +1088,9 @@ expr:
|
||||
| expr T_IS_SMALLER_OR_EQUAL expr { $$ = Expr\BinaryOp\SmallerOrEqual[$1, $3]; }
|
||||
| expr '>' expr { $$ = Expr\BinaryOp\Greater [$1, $3]; }
|
||||
| expr T_IS_GREATER_OR_EQUAL expr { $$ = Expr\BinaryOp\GreaterOrEqual[$1, $3]; }
|
||||
#if PHP8
|
||||
| expr T_PIPE expr { $$ = Expr\BinaryOp\Pipe[$1, $3]; }
|
||||
#endif
|
||||
| expr T_INSTANCEOF class_name_reference { $$ = Expr\Instanceof_[$1, $3]; }
|
||||
| '(' expr ')' { $$ = $2; }
|
||||
| expr '?' expr ':' expr { $$ = Expr\Ternary[$1, $3, $5]; }
|
||||
@@ -1045,10 +1113,9 @@ expr:
|
||||
| T_OBJECT_CAST expr { $$ = Expr\Cast\Object_ [$2]; }
|
||||
| T_BOOL_CAST expr { $$ = Expr\Cast\Bool_ [$2]; }
|
||||
| T_UNSET_CAST expr { $$ = Expr\Cast\Unset_ [$2]; }
|
||||
| T_EXIT exit_expr
|
||||
{ $attrs = attributes();
|
||||
$attrs['kind'] = strtolower($1) === 'exit' ? Expr\Exit_::KIND_EXIT : Expr\Exit_::KIND_DIE;
|
||||
$$ = new Expr\Exit_($2, $attrs); }
|
||||
| T_VOID_CAST expr { $$ = Expr\Cast\Void_ [$2]; }
|
||||
| T_EXIT ctor_arguments
|
||||
{ $$ = $this->createExitExpr($1, #1, $2, attributes()); }
|
||||
| '@' expr { $$ = Expr\ErrorSuppress[$2]; }
|
||||
| scalar
|
||||
| '`' backticks_expr '`' { $$ = Expr\ShellExec[$2]; }
|
||||
@@ -1084,12 +1151,21 @@ anonymous_class:
|
||||
$this->checkClass($$[0], -1); }
|
||||
;
|
||||
|
||||
new_expr:
|
||||
T_NEW class_name_reference ctor_arguments { $$ = Expr\New_[$2, $3]; }
|
||||
new_dereferenceable:
|
||||
T_NEW class_name_reference argument_list { $$ = Expr\New_[$2, $3]; }
|
||||
| T_NEW anonymous_class
|
||||
{ list($class, $ctorArgs) = $2; $$ = Expr\New_[$class, $ctorArgs]; }
|
||||
;
|
||||
|
||||
new_non_dereferenceable:
|
||||
T_NEW class_name_reference { $$ = Expr\New_[$2, []]; }
|
||||
;
|
||||
|
||||
new_expr:
|
||||
new_dereferenceable
|
||||
| new_non_dereferenceable
|
||||
;
|
||||
|
||||
lexical_vars:
|
||||
/* empty */ { $$ = array(); }
|
||||
| T_USE '(' lexical_var_list ')' { $$ = $3; }
|
||||
@@ -1141,19 +1217,14 @@ class_name_reference:
|
||||
|
||||
class_name_or_var:
|
||||
class_name
|
||||
| fully_dereferencable
|
||||
;
|
||||
|
||||
exit_expr:
|
||||
/* empty */ { $$ = null; }
|
||||
| '(' optional_expr ')' { $$ = $2; }
|
||||
| fully_dereferenceable
|
||||
;
|
||||
|
||||
backticks_expr:
|
||||
/* empty */ { $$ = array(); }
|
||||
| T_ENCAPSED_AND_WHITESPACE
|
||||
{ $$ = array(Node\InterpolatedStringPart[Scalar\String_::parseEscapeSequences($1, '`')]); }
|
||||
| encaps_list { parseEncapsed($1, '`', true); $$ = $1; }
|
||||
| encaps_string_part
|
||||
{ $$ = array($1); parseEncapsed($$, '`', $this->phpVersion->supportsUnicodeEscapes()); }
|
||||
| encaps_list { parseEncapsed($1, '`', $this->phpVersion->supportsUnicodeEscapes()); $$ = $1; }
|
||||
;
|
||||
|
||||
ctor_arguments:
|
||||
@@ -1171,6 +1242,7 @@ constant:
|
||||
| T_METHOD_C { $$ = Scalar\MagicConst\Method[]; }
|
||||
| T_FUNC_C { $$ = Scalar\MagicConst\Function_[]; }
|
||||
| T_NS_C { $$ = Scalar\MagicConst\Namespace_[]; }
|
||||
| T_PROPERTY_C { $$ = Scalar\MagicConst\Property[]; }
|
||||
;
|
||||
|
||||
class_constant:
|
||||
@@ -1190,23 +1262,24 @@ array_short_syntax:
|
||||
$$ = new Expr\Array_($2, $attrs); }
|
||||
;
|
||||
|
||||
dereferencable_scalar:
|
||||
dereferenceable_scalar:
|
||||
T_ARRAY '(' array_pair_list ')'
|
||||
{ $attrs = attributes(); $attrs['kind'] = Expr\Array_::KIND_LONG;
|
||||
$$ = new Expr\Array_($3, $attrs);
|
||||
$this->createdArrays->attach($$); }
|
||||
| array_short_syntax { $$ = $1; $this->createdArrays->attach($$); }
|
||||
| T_CONSTANT_ENCAPSED_STRING { $$ = Scalar\String_::fromString($1, attributes()); }
|
||||
| T_CONSTANT_ENCAPSED_STRING
|
||||
{ $$ = Scalar\String_::fromString($1, attributes(), $this->phpVersion->supportsUnicodeEscapes()); }
|
||||
| '"' encaps_list '"'
|
||||
{ $attrs = attributes(); $attrs['kind'] = Scalar\String_::KIND_DOUBLE_QUOTED;
|
||||
parseEncapsed($2, '"', true); $$ = new Scalar\InterpolatedString($2, $attrs); }
|
||||
parseEncapsed($2, '"', $this->phpVersion->supportsUnicodeEscapes()); $$ = new Scalar\InterpolatedString($2, $attrs); }
|
||||
;
|
||||
|
||||
scalar:
|
||||
T_LNUMBER
|
||||
{ $$ = $this->parseLNumber($1, attributes(), $this->phpVersion->allowsInvalidOctals()); }
|
||||
| T_DNUMBER { $$ = Scalar\Float_::fromString($1, attributes()); }
|
||||
| dereferencable_scalar
|
||||
| dereferenceable_scalar
|
||||
| constant
|
||||
| class_constant
|
||||
| T_START_HEREDOC T_ENCAPSED_AND_WHITESPACE T_END_HEREDOC
|
||||
@@ -1222,32 +1295,36 @@ optional_expr:
|
||||
| expr
|
||||
;
|
||||
|
||||
fully_dereferencable:
|
||||
fully_dereferenceable:
|
||||
variable
|
||||
| '(' expr ')' { $$ = $2; }
|
||||
| dereferencable_scalar
|
||||
| dereferenceable_scalar
|
||||
| class_constant
|
||||
| new_dereferenceable
|
||||
;
|
||||
|
||||
array_object_dereferencable:
|
||||
fully_dereferencable
|
||||
array_object_dereferenceable:
|
||||
fully_dereferenceable
|
||||
| constant
|
||||
;
|
||||
|
||||
callable_expr:
|
||||
callable_variable
|
||||
| '(' expr ')' { $$ = $2; }
|
||||
| dereferencable_scalar
|
||||
| dereferenceable_scalar
|
||||
| new_dereferenceable
|
||||
;
|
||||
|
||||
callable_variable:
|
||||
simple_variable
|
||||
| array_object_dereferencable '[' optional_expr ']' { $$ = Expr\ArrayDimFetch[$1, $3]; }
|
||||
| array_object_dereferencable '{' expr '}' { $$ = Expr\ArrayDimFetch[$1, $3]; }
|
||||
| array_object_dereferenceable '[' optional_expr ']' { $$ = Expr\ArrayDimFetch[$1, $3]; }
|
||||
#if PHP7
|
||||
| array_object_dereferenceable '{' expr '}' { $$ = Expr\ArrayDimFetch[$1, $3]; }
|
||||
#endif
|
||||
| function_call
|
||||
| array_object_dereferencable T_OBJECT_OPERATOR property_name argument_list
|
||||
| array_object_dereferenceable T_OBJECT_OPERATOR property_name argument_list
|
||||
{ $$ = Expr\MethodCall[$1, $3, $4]; }
|
||||
| array_object_dereferencable T_NULLSAFE_OBJECT_OPERATOR property_name argument_list
|
||||
| array_object_dereferenceable T_NULLSAFE_OBJECT_OPERATOR property_name argument_list
|
||||
{ $$ = Expr\NullsafeMethodCall[$1, $3, $4]; }
|
||||
;
|
||||
|
||||
@@ -1259,9 +1336,9 @@ optional_plain_variable:
|
||||
variable:
|
||||
callable_variable
|
||||
| static_member
|
||||
| array_object_dereferencable T_OBJECT_OPERATOR property_name
|
||||
| array_object_dereferenceable T_OBJECT_OPERATOR property_name
|
||||
{ $$ = Expr\PropertyFetch[$1, $3]; }
|
||||
| array_object_dereferencable T_NULLSAFE_OBJECT_OPERATOR property_name
|
||||
| array_object_dereferenceable T_NULLSAFE_OBJECT_OPERATOR property_name
|
||||
{ $$ = Expr\NullsafePropertyFetch[$1, $3]; }
|
||||
;
|
||||
|
||||
@@ -1285,7 +1362,9 @@ static_member:
|
||||
new_variable:
|
||||
simple_variable
|
||||
| new_variable '[' optional_expr ']' { $$ = Expr\ArrayDimFetch[$1, $3]; }
|
||||
#if PHP7
|
||||
| new_variable '{' expr '}' { $$ = Expr\ArrayDimFetch[$1, $3]; }
|
||||
#endif
|
||||
| new_variable T_OBJECT_OPERATOR property_name { $$ = Expr\PropertyFetch[$1, $3]; }
|
||||
| new_variable T_NULLSAFE_OBJECT_OPERATOR property_name { $$ = Expr\NullsafePropertyFetch[$1, $3]; }
|
||||
| class_name T_PAAMAYIM_NEKUDOTAYIM static_member_prop_name
|
||||
@@ -1352,7 +1431,8 @@ encaps_list:
|
||||
;
|
||||
|
||||
encaps_string_part:
|
||||
T_ENCAPSED_AND_WHITESPACE { $$ = Node\InterpolatedStringPart[$1]; }
|
||||
T_ENCAPSED_AND_WHITESPACE
|
||||
{ $attrs = attributes(); $attrs['rawValue'] = $1; $$ = new Node\InterpolatedStringPart($1, $attrs); }
|
||||
;
|
||||
|
||||
encaps_str_varname:
|
||||
|
@@ -23,6 +23,7 @@ function preprocessGrammar($code) {
|
||||
$code = resolveNodes($code);
|
||||
$code = resolveMacros($code);
|
||||
$code = resolveStackAccess($code);
|
||||
$code = str_replace('$this', '$self', $code);
|
||||
|
||||
return $code;
|
||||
}
|
||||
@@ -87,14 +88,15 @@ function resolveMacros($code) {
|
||||
if ('pushNormalizing' === $name) {
|
||||
assertArgs(2, $args, $name);
|
||||
|
||||
return 'if (is_array(' . $args[1] . ')) { $$ = array_merge(' . $args[0] . ', ' . $args[1] . '); }'
|
||||
. ' else { ' . $args[0] . '[] = ' . $args[1] . '; $$ = ' . $args[0] . '; }';
|
||||
return 'if (' . $args[1] . ' !== null) { ' . $args[0] . '[] = ' . $args[1] . '; } $$ = ' . $args[0] . ';';
|
||||
}
|
||||
|
||||
if ('toArray' == $name) {
|
||||
if ('toBlock' == $name) {
|
||||
assertArgs(1, $args, $name);
|
||||
|
||||
return 'is_array(' . $args[0] . ') ? ' . $args[0] . ' : array(' . $args[0] . ')';
|
||||
return 'if (' . $args[0] . ' instanceof Stmt\Block) { $$ = ' . $args[0] . '->stmts; } '
|
||||
. 'else if (' . $args[0] . ' === null) { $$ = []; } '
|
||||
. 'else { $$ = [' . $args[0] . ']; }';
|
||||
}
|
||||
|
||||
if ('parseVar' === $name) {
|
||||
@@ -122,15 +124,6 @@ function resolveMacros($code) {
|
||||
return $args[0] . ' = $this->maybeCreateZeroLengthNop($this->tokenPos);';
|
||||
}
|
||||
|
||||
if ('prependLeadingComments' === $name) {
|
||||
assertArgs(1, $args, $name);
|
||||
|
||||
return '$comments = $this->getCommentsBeforeToken($this->tokenStartStack[#1]); $stmts = ' . $args[0] . '; '
|
||||
. 'if (!empty($comments)) {'
|
||||
. '$stmts[0]->setAttribute(\'comments\', '
|
||||
. 'array_merge($comments, $stmts[0]->getAttribute(\'comments\', []))); }';
|
||||
}
|
||||
|
||||
return $matches[0];
|
||||
},
|
||||
$code
|
||||
|
@@ -28,7 +28,7 @@ class ClassConst implements PhpParser\Builder {
|
||||
* Creates a class constant builder
|
||||
*
|
||||
* @param string|Identifier $name Name
|
||||
* @param Node\Expr|bool|null|int|float|string|array $value Value
|
||||
* @param Node\Expr|bool|null|int|float|string|array|\UnitEnum $value Value
|
||||
*/
|
||||
public function __construct($name, $value) {
|
||||
$this->constants = [new Const_($name, BuilderHelpers::normalizeValue($value))];
|
||||
@@ -38,7 +38,7 @@ class ClassConst implements PhpParser\Builder {
|
||||
* Add another constant to const group
|
||||
*
|
||||
* @param string|Identifier $name Name
|
||||
* @param Node\Expr|bool|null|int|float|string|array $value Value
|
||||
* @param Node\Expr|bool|null|int|float|string|array|\UnitEnum $value Value
|
||||
*
|
||||
* @return $this The builder instance (for fluid interface)
|
||||
*/
|
||||
|
@@ -13,7 +13,6 @@ use PhpParser\Node\Stmt;
|
||||
class EnumCase implements PhpParser\Builder {
|
||||
/** @var Identifier|string */
|
||||
protected $name;
|
||||
/** @var ?Node\Expr */
|
||||
protected ?Node\Expr $value = null;
|
||||
/** @var array<string, mixed> */
|
||||
protected array $attributes = [];
|
||||
|
@@ -122,6 +122,28 @@ class Param implements PhpParser\Builder {
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gives the promoted property private(set) visibility.
|
||||
*
|
||||
* @return $this The builder instance (for fluid interface)
|
||||
*/
|
||||
public function makePrivateSet() {
|
||||
$this->flags = BuilderHelpers::addModifier($this->flags, Modifiers::PRIVATE_SET);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gives the promoted property protected(set) visibility.
|
||||
*
|
||||
* @return $this The builder instance (for fluid interface)
|
||||
*/
|
||||
public function makeProtectedSet() {
|
||||
$this->flags = BuilderHelpers::addModifier($this->flags, Modifiers::PROTECTED_SET);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an attribute group.
|
||||
*
|
||||
|
@@ -23,6 +23,8 @@ class Property implements PhpParser\Builder {
|
||||
protected ?Node $type = null;
|
||||
/** @var list<Node\AttributeGroup> */
|
||||
protected array $attributeGroups = [];
|
||||
/** @var list<Node\PropertyHook> */
|
||||
protected array $hooks = [];
|
||||
|
||||
/**
|
||||
* Creates a property builder.
|
||||
@@ -88,6 +90,50 @@ class Property implements PhpParser\Builder {
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes the property abstract. Requires at least one property hook to be specified as well.
|
||||
*
|
||||
* @return $this The builder instance (for fluid interface)
|
||||
*/
|
||||
public function makeAbstract() {
|
||||
$this->flags = BuilderHelpers::addModifier($this->flags, Modifiers::ABSTRACT);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes the property final.
|
||||
*
|
||||
* @return $this The builder instance (for fluid interface)
|
||||
*/
|
||||
public function makeFinal() {
|
||||
$this->flags = BuilderHelpers::addModifier($this->flags, Modifiers::FINAL);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gives the property private(set) visibility.
|
||||
*
|
||||
* @return $this The builder instance (for fluid interface)
|
||||
*/
|
||||
public function makePrivateSet() {
|
||||
$this->flags = BuilderHelpers::addModifier($this->flags, Modifiers::PRIVATE_SET);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gives the property protected(set) visibility.
|
||||
*
|
||||
* @return $this The builder instance (for fluid interface)
|
||||
*/
|
||||
public function makeProtectedSet() {
|
||||
$this->flags = BuilderHelpers::addModifier($this->flags, Modifiers::PROTECTED_SET);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets default value for the property.
|
||||
*
|
||||
@@ -142,12 +188,27 @@ class Property implements PhpParser\Builder {
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a property hook.
|
||||
*
|
||||
* @return $this The builder instance (for fluid interface)
|
||||
*/
|
||||
public function addHook(Node\PropertyHook $hook) {
|
||||
$this->hooks[] = $hook;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the built class node.
|
||||
*
|
||||
* @return Stmt\Property The built property node
|
||||
*/
|
||||
public function getNode(): PhpParser\Node {
|
||||
if ($this->flags & Modifiers::ABSTRACT && !$this->hooks) {
|
||||
throw new PhpParser\Error('Only hooked properties may be declared abstract');
|
||||
}
|
||||
|
||||
return new Stmt\Property(
|
||||
$this->flags !== 0 ? $this->flags : Modifiers::PUBLIC,
|
||||
[
|
||||
@@ -155,7 +216,8 @@ class Property implements PhpParser\Builder {
|
||||
],
|
||||
$this->attributes,
|
||||
$this->type,
|
||||
$this->attributeGroups
|
||||
$this->attributeGroups,
|
||||
$this->hooks
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@@ -210,7 +210,7 @@ class BuilderFactory {
|
||||
/**
|
||||
* Creates node a for a literal value.
|
||||
*
|
||||
* @param Expr|bool|null|int|float|string|array $value $value
|
||||
* @param Expr|bool|null|int|float|string|array|\UnitEnum $value $value
|
||||
*/
|
||||
public function val($value): Expr {
|
||||
return BuilderHelpers::normalizeValue($value);
|
||||
|
@@ -6,6 +6,7 @@ use PhpParser\Node\ComplexType;
|
||||
use PhpParser\Node\Expr;
|
||||
use PhpParser\Node\Identifier;
|
||||
use PhpParser\Node\Name;
|
||||
use PhpParser\Node\Name\FullyQualified;
|
||||
use PhpParser\Node\NullableType;
|
||||
use PhpParser\Node\Scalar;
|
||||
use PhpParser\Node\Stmt;
|
||||
@@ -214,7 +215,7 @@ final class BuilderHelpers {
|
||||
* Normalizes a value: Converts nulls, booleans, integers,
|
||||
* floats, strings and arrays into their respective nodes
|
||||
*
|
||||
* @param Node\Expr|bool|null|int|float|string|array $value The value to normalize
|
||||
* @param Node\Expr|bool|null|int|float|string|array|\UnitEnum $value The value to normalize
|
||||
*
|
||||
* @return Expr The normalized value
|
||||
*/
|
||||
@@ -268,6 +269,10 @@ final class BuilderHelpers {
|
||||
return new Expr\Array_($items);
|
||||
}
|
||||
|
||||
if ($value instanceof \UnitEnum) {
|
||||
return new Expr\ClassConstFetch(new FullyQualified(\get_class($value)), new Identifier($value->name));
|
||||
}
|
||||
|
||||
throw new \LogicException('Invalid value');
|
||||
}
|
||||
|
||||
|
@@ -46,6 +46,7 @@ class Comment implements \JsonSerializable {
|
||||
* Gets the line number the comment started on.
|
||||
*
|
||||
* @return int Line number (or -1 if not available)
|
||||
* @phpstan-return -1|positive-int
|
||||
*/
|
||||
public function getStartLine(): int {
|
||||
return $this->startLine;
|
||||
@@ -73,6 +74,7 @@ class Comment implements \JsonSerializable {
|
||||
* Gets the line number the comment ends on.
|
||||
*
|
||||
* @return int Line number (or -1 if not available)
|
||||
* @phpstan-return -1|positive-int
|
||||
*/
|
||||
public function getEndLine(): int {
|
||||
return $this->endLine;
|
||||
|
@@ -215,6 +215,9 @@ class ConstExprEvaluator {
|
||||
case '<': return $this->evaluate($l) < $this->evaluate($r);
|
||||
case '<=': return $this->evaluate($l) <= $this->evaluate($r);
|
||||
case '<=>': return $this->evaluate($l) <=> $this->evaluate($r);
|
||||
case '|>':
|
||||
$lval = $this->evaluate($l);
|
||||
return $this->evaluate($r)($lval);
|
||||
}
|
||||
|
||||
throw new \Exception('Should not happen');
|
||||
|
@@ -32,6 +32,7 @@ class Error extends \RuntimeException {
|
||||
* Gets the line the error starts in.
|
||||
*
|
||||
* @return int Error start line
|
||||
* @phpstan-return -1|positive-int
|
||||
*/
|
||||
public function getStartLine(): int {
|
||||
return $this->attributes['startLine'] ?? -1;
|
||||
@@ -41,6 +42,7 @@ class Error extends \RuntimeException {
|
||||
* Gets the line the error ends in.
|
||||
*
|
||||
* @return int Error end line
|
||||
* @phpstan-return -1|positive-int
|
||||
*/
|
||||
public function getEndLine(): int {
|
||||
return $this->attributes['endLine'] ?? -1;
|
||||
|
@@ -20,9 +20,9 @@ class TokenStream {
|
||||
*
|
||||
* @param Token[] $tokens Tokens in PhpToken::tokenize() format
|
||||
*/
|
||||
public function __construct(array $tokens) {
|
||||
public function __construct(array $tokens, int $tabWidth) {
|
||||
$this->tokens = $tokens;
|
||||
$this->indentMap = $this->calcIndentMap();
|
||||
$this->indentMap = $this->calcIndentMap($tabWidth);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -195,12 +195,6 @@ class TokenStream {
|
||||
return false;
|
||||
}
|
||||
|
||||
public function haveBracesInRange(int $startPos, int $endPos): bool {
|
||||
return $this->haveTokenInRange($startPos, $endPos, '{')
|
||||
|| $this->haveTokenInRange($startPos, $endPos, T_CURLY_OPEN)
|
||||
|| $this->haveTokenInRange($startPos, $endPos, '}');
|
||||
}
|
||||
|
||||
public function haveTagInRange(int $startPos, int $endPos): bool {
|
||||
return $this->haveTokenInRange($startPos, $endPos, \T_OPEN_TAG)
|
||||
|| $this->haveTokenInRange($startPos, $endPos, \T_CLOSE_TAG);
|
||||
@@ -254,17 +248,21 @@ class TokenStream {
|
||||
*
|
||||
* @return int[] Token position to indentation map
|
||||
*/
|
||||
private function calcIndentMap(): array {
|
||||
private function calcIndentMap(int $tabWidth): array {
|
||||
$indentMap = [];
|
||||
$indent = 0;
|
||||
foreach ($this->tokens as $token) {
|
||||
foreach ($this->tokens as $i => $token) {
|
||||
$indentMap[] = $indent;
|
||||
|
||||
if ($token->id === \T_WHITESPACE) {
|
||||
$content = $token->text;
|
||||
$newlinePos = \strrpos($content, "\n");
|
||||
if (false !== $newlinePos) {
|
||||
$indent = \strlen($content) - $newlinePos - 1;
|
||||
$indent = $this->getIndent(\substr($content, $newlinePos + 1), $tabWidth);
|
||||
} elseif ($i === 1 && $this->tokens[0]->id === \T_OPEN_TAG &&
|
||||
$this->tokens[0]->text[\strlen($this->tokens[0]->text) - 1] === "\n") {
|
||||
// Special case: Newline at the end of opening tag followed by whitespace.
|
||||
$indent = $this->getIndent($content, $tabWidth);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -274,4 +272,11 @@ class TokenStream {
|
||||
|
||||
return $indentMap;
|
||||
}
|
||||
|
||||
private function getIndent(string $ws, int $tabWidth): int {
|
||||
$spaces = \substr_count($ws, " ");
|
||||
$tabs = \substr_count($ws, "\t");
|
||||
assert(\strlen($ws) === $spaces + $tabs);
|
||||
return $spaces + $tabs * $tabWidth;
|
||||
}
|
||||
}
|
||||
|
@@ -76,7 +76,7 @@ class Lexer {
|
||||
$numTokens = \count($tokens);
|
||||
if ($numTokens === 0) {
|
||||
// Empty input edge case: Just add the sentinel token.
|
||||
$tokens[] = [new Token(0, "\0", 1, 0)];
|
||||
$tokens[] = new Token(0, "\0", 1, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@@ -5,19 +5,19 @@ namespace PhpParser\Lexer;
|
||||
use PhpParser\Error;
|
||||
use PhpParser\ErrorHandler;
|
||||
use PhpParser\Lexer;
|
||||
use PhpParser\Lexer\TokenEmulator\AsymmetricVisibilityTokenEmulator;
|
||||
use PhpParser\Lexer\TokenEmulator\AttributeEmulator;
|
||||
use PhpParser\Lexer\TokenEmulator\EnumTokenEmulator;
|
||||
use PhpParser\Lexer\TokenEmulator\CoaleseEqualTokenEmulator;
|
||||
use PhpParser\Lexer\TokenEmulator\ExplicitOctalEmulator;
|
||||
use PhpParser\Lexer\TokenEmulator\FlexibleDocStringEmulator;
|
||||
use PhpParser\Lexer\TokenEmulator\FnTokenEmulator;
|
||||
use PhpParser\Lexer\TokenEmulator\MatchTokenEmulator;
|
||||
use PhpParser\Lexer\TokenEmulator\NullsafeTokenEmulator;
|
||||
use PhpParser\Lexer\TokenEmulator\NumericLiteralSeparatorEmulator;
|
||||
use PhpParser\Lexer\TokenEmulator\PipeOperatorEmulator;
|
||||
use PhpParser\Lexer\TokenEmulator\PropertyTokenEmulator;
|
||||
use PhpParser\Lexer\TokenEmulator\ReadonlyFunctionTokenEmulator;
|
||||
use PhpParser\Lexer\TokenEmulator\ReadonlyTokenEmulator;
|
||||
use PhpParser\Lexer\TokenEmulator\ReverseEmulator;
|
||||
use PhpParser\Lexer\TokenEmulator\TokenEmulator;
|
||||
use PhpParser\Lexer\TokenEmulator\VoidCastEmulator;
|
||||
use PhpParser\PhpVersion;
|
||||
use PhpParser\Token;
|
||||
|
||||
@@ -47,6 +47,10 @@ class Emulative extends Lexer {
|
||||
new ReadonlyTokenEmulator(),
|
||||
new ExplicitOctalEmulator(),
|
||||
new ReadonlyFunctionTokenEmulator(),
|
||||
new PropertyTokenEmulator(),
|
||||
new AsymmetricVisibilityTokenEmulator(),
|
||||
new PipeOperatorEmulator(),
|
||||
new VoidCastEmulator(),
|
||||
];
|
||||
|
||||
// Collect emulators that are relevant for the PHP version we're running
|
||||
|
@@ -0,0 +1,93 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser\Lexer\TokenEmulator;
|
||||
|
||||
use PhpParser\PhpVersion;
|
||||
use PhpParser\Token;
|
||||
|
||||
final class AsymmetricVisibilityTokenEmulator extends TokenEmulator {
|
||||
public function getPhpVersion(): PhpVersion {
|
||||
return PhpVersion::fromComponents(8, 4);
|
||||
}
|
||||
public function isEmulationNeeded(string $code): bool {
|
||||
$code = strtolower($code);
|
||||
return strpos($code, 'public(set)') !== false ||
|
||||
strpos($code, 'protected(set)') !== false ||
|
||||
strpos($code, 'private(set)') !== false;
|
||||
}
|
||||
|
||||
public function emulate(string $code, array $tokens): array {
|
||||
$map = [
|
||||
\T_PUBLIC => \T_PUBLIC_SET,
|
||||
\T_PROTECTED => \T_PROTECTED_SET,
|
||||
\T_PRIVATE => \T_PRIVATE_SET,
|
||||
];
|
||||
for ($i = 0, $c = count($tokens); $i < $c; ++$i) {
|
||||
$token = $tokens[$i];
|
||||
if (isset($map[$token->id]) && $i + 3 < $c && $tokens[$i + 1]->text === '(' &&
|
||||
$tokens[$i + 2]->id === \T_STRING && \strtolower($tokens[$i + 2]->text) === 'set' &&
|
||||
$tokens[$i + 3]->text === ')' &&
|
||||
$this->isKeywordContext($tokens, $i)
|
||||
) {
|
||||
array_splice($tokens, $i, 4, [
|
||||
new Token(
|
||||
$map[$token->id], $token->text . '(' . $tokens[$i + 2]->text . ')',
|
||||
$token->line, $token->pos),
|
||||
]);
|
||||
$c -= 3;
|
||||
}
|
||||
}
|
||||
|
||||
return $tokens;
|
||||
}
|
||||
|
||||
public function reverseEmulate(string $code, array $tokens): array {
|
||||
$reverseMap = [
|
||||
\T_PUBLIC_SET => \T_PUBLIC,
|
||||
\T_PROTECTED_SET => \T_PROTECTED,
|
||||
\T_PRIVATE_SET => \T_PRIVATE,
|
||||
];
|
||||
for ($i = 0, $c = count($tokens); $i < $c; ++$i) {
|
||||
$token = $tokens[$i];
|
||||
if (isset($reverseMap[$token->id]) &&
|
||||
\preg_match('/(public|protected|private)\((set)\)/i', $token->text, $matches)
|
||||
) {
|
||||
[, $modifier, $set] = $matches;
|
||||
$modifierLen = \strlen($modifier);
|
||||
array_splice($tokens, $i, 1, [
|
||||
new Token($reverseMap[$token->id], $modifier, $token->line, $token->pos),
|
||||
new Token(\ord('('), '(', $token->line, $token->pos + $modifierLen),
|
||||
new Token(\T_STRING, $set, $token->line, $token->pos + $modifierLen + 1),
|
||||
new Token(\ord(')'), ')', $token->line, $token->pos + $modifierLen + 4),
|
||||
]);
|
||||
$i += 3;
|
||||
$c += 3;
|
||||
}
|
||||
}
|
||||
|
||||
return $tokens;
|
||||
}
|
||||
|
||||
/** @param Token[] $tokens */
|
||||
protected function isKeywordContext(array $tokens, int $pos): bool {
|
||||
$prevToken = $this->getPreviousNonSpaceToken($tokens, $pos);
|
||||
if ($prevToken === null) {
|
||||
return false;
|
||||
}
|
||||
return $prevToken->id !== \T_OBJECT_OPERATOR
|
||||
&& $prevToken->id !== \T_NULLSAFE_OBJECT_OPERATOR;
|
||||
}
|
||||
|
||||
/** @param Token[] $tokens */
|
||||
private function getPreviousNonSpaceToken(array $tokens, int $start): ?Token {
|
||||
for ($i = $start - 1; $i >= 0; --$i) {
|
||||
if ($tokens[$i]->id === T_WHITESPACE) {
|
||||
continue;
|
||||
}
|
||||
|
||||
return $tokens[$i];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
@@ -14,8 +14,12 @@ abstract class KeywordEmulator extends TokenEmulator {
|
||||
|
||||
/** @param Token[] $tokens */
|
||||
protected function isKeywordContext(array $tokens, int $pos): bool {
|
||||
$previousNonSpaceToken = $this->getPreviousNonSpaceToken($tokens, $pos);
|
||||
return $previousNonSpaceToken === null || $previousNonSpaceToken->id !== \T_OBJECT_OPERATOR;
|
||||
$prevToken = $this->getPreviousNonSpaceToken($tokens, $pos);
|
||||
if ($prevToken === null) {
|
||||
return false;
|
||||
}
|
||||
return $prevToken->id !== \T_OBJECT_OPERATOR
|
||||
&& $prevToken->id !== \T_NULLSAFE_OBJECT_OPERATOR;
|
||||
}
|
||||
|
||||
public function emulate(string $code, array $tokens): array {
|
||||
|
45
lib/PhpParser/Lexer/TokenEmulator/PipeOperatorEmulator.php
Normal file
45
lib/PhpParser/Lexer/TokenEmulator/PipeOperatorEmulator.php
Normal file
@@ -0,0 +1,45 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser\Lexer\TokenEmulator;
|
||||
|
||||
use PhpParser\Lexer\TokenEmulator\TokenEmulator;
|
||||
use PhpParser\PhpVersion;
|
||||
use PhpParser\Token;
|
||||
|
||||
class PipeOperatorEmulator extends TokenEmulator {
|
||||
public function getPhpVersion(): PhpVersion {
|
||||
return PhpVersion::fromComponents(8, 5);
|
||||
}
|
||||
|
||||
public function isEmulationNeeded(string $code): bool {
|
||||
return \strpos($code, '|>') !== false;
|
||||
}
|
||||
|
||||
public function emulate(string $code, array $tokens): array {
|
||||
for ($i = 0, $c = count($tokens); $i < $c; ++$i) {
|
||||
$token = $tokens[$i];
|
||||
if ($token->text === '|' && isset($tokens[$i + 1]) && $tokens[$i + 1]->text === '>') {
|
||||
array_splice($tokens, $i, 2, [
|
||||
new Token(\T_PIPE, '|>', $token->line, $token->pos),
|
||||
]);
|
||||
$c--;
|
||||
}
|
||||
}
|
||||
return $tokens;
|
||||
}
|
||||
|
||||
public function reverseEmulate(string $code, array $tokens): array {
|
||||
for ($i = 0, $c = count($tokens); $i < $c; ++$i) {
|
||||
$token = $tokens[$i];
|
||||
if ($token->id === \T_PIPE) {
|
||||
array_splice($tokens, $i, 1, [
|
||||
new Token(\ord('|'), '|', $token->line, $token->pos),
|
||||
new Token(\ord('>'), '>', $token->line, $token->pos + 1),
|
||||
]);
|
||||
$i++;
|
||||
$c++;
|
||||
}
|
||||
}
|
||||
return $tokens;
|
||||
}
|
||||
}
|
19
lib/PhpParser/Lexer/TokenEmulator/PropertyTokenEmulator.php
Normal file
19
lib/PhpParser/Lexer/TokenEmulator/PropertyTokenEmulator.php
Normal file
@@ -0,0 +1,19 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser\Lexer\TokenEmulator;
|
||||
|
||||
use PhpParser\PhpVersion;
|
||||
|
||||
final class PropertyTokenEmulator extends KeywordEmulator {
|
||||
public function getPhpVersion(): PhpVersion {
|
||||
return PhpVersion::fromComponents(8, 4);
|
||||
}
|
||||
|
||||
public function getKeywordString(): string {
|
||||
return '__property__';
|
||||
}
|
||||
|
||||
public function getKeywordToken(): int {
|
||||
return \T_PROPERTY_C;
|
||||
}
|
||||
}
|
98
lib/PhpParser/Lexer/TokenEmulator/VoidCastEmulator.php
Normal file
98
lib/PhpParser/Lexer/TokenEmulator/VoidCastEmulator.php
Normal file
@@ -0,0 +1,98 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser\Lexer\TokenEmulator;
|
||||
|
||||
use PhpParser\PhpVersion;
|
||||
use PhpParser\Token;
|
||||
|
||||
class VoidCastEmulator extends TokenEmulator {
|
||||
public function getPhpVersion(): PhpVersion {
|
||||
return PhpVersion::fromComponents(8, 5);
|
||||
}
|
||||
|
||||
public function isEmulationNeeded(string $code): bool {
|
||||
return (bool)\preg_match('/\([ \t]*void[ \t]*\)/i', $code);
|
||||
}
|
||||
|
||||
public function emulate(string $code, array $tokens): array {
|
||||
for ($i = 0, $c = count($tokens); $i < $c; ++$i) {
|
||||
$token = $tokens[$i];
|
||||
if ($token->text !== '(') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$numTokens = 1;
|
||||
$text = '(';
|
||||
$j = $i + 1;
|
||||
if ($j < $c && $tokens[$j]->id === \T_WHITESPACE && preg_match('/[ \t]+/', $tokens[$j]->text)) {
|
||||
$text .= $tokens[$j]->text;
|
||||
$numTokens++;
|
||||
$j++;
|
||||
}
|
||||
|
||||
if ($j >= $c || $tokens[$j]->id !== \T_STRING || \strtolower($tokens[$j]->text) !== 'void') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$text .= $tokens[$j]->text;
|
||||
$numTokens++;
|
||||
$k = $j + 1;
|
||||
if ($k < $c && $tokens[$k]->id === \T_WHITESPACE && preg_match('/[ \t]+/', $tokens[$k]->text)) {
|
||||
$text .= $tokens[$k]->text;
|
||||
$numTokens++;
|
||||
$k++;
|
||||
}
|
||||
|
||||
if ($k >= $c || $tokens[$k]->text !== ')') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$text .= ')';
|
||||
$numTokens++;
|
||||
array_splice($tokens, $i, $numTokens, [
|
||||
new Token(\T_VOID_CAST, $text, $token->line, $token->pos),
|
||||
]);
|
||||
$c -= $numTokens - 1;
|
||||
}
|
||||
return $tokens;
|
||||
}
|
||||
|
||||
public function reverseEmulate(string $code, array $tokens): array {
|
||||
for ($i = 0, $c = count($tokens); $i < $c; ++$i) {
|
||||
$token = $tokens[$i];
|
||||
if ($token->id !== \T_VOID_CAST) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!preg_match('/^\(([ \t]*)(void)([ \t]*)\)$/i', $token->text, $match)) {
|
||||
throw new \LogicException('Unexpected T_VOID_CAST contents');
|
||||
}
|
||||
|
||||
$newTokens = [];
|
||||
$pos = $token->pos;
|
||||
|
||||
$newTokens[] = new Token(\ord('('), '(', $token->line, $pos);
|
||||
$pos++;
|
||||
|
||||
if ($match[1] !== '') {
|
||||
$newTokens[] = new Token(\T_WHITESPACE, $match[1], $token->line, $pos);
|
||||
$pos += \strlen($match[1]);
|
||||
}
|
||||
|
||||
$newTokens[] = new Token(\T_STRING, $match[2], $token->line, $pos);
|
||||
$pos += \strlen($match[2]);
|
||||
|
||||
if ($match[3] !== '') {
|
||||
$newTokens[] = new Token(\T_WHITESPACE, $match[3], $token->line, $pos);
|
||||
$pos += \strlen($match[3]);
|
||||
}
|
||||
|
||||
$newTokens[] = new Token(\ord(')'), ')', $token->line, $pos);
|
||||
|
||||
array_splice($tokens, $i, 1, $newTokens);
|
||||
$i += \count($newTokens) - 1;
|
||||
$c += \count($newTokens) - 1;
|
||||
}
|
||||
return $tokens;
|
||||
}
|
||||
}
|
@@ -14,23 +14,47 @@ final class Modifiers {
|
||||
public const ABSTRACT = 16;
|
||||
public const FINAL = 32;
|
||||
public const READONLY = 64;
|
||||
public const PUBLIC_SET = 128;
|
||||
public const PROTECTED_SET = 256;
|
||||
public const PRIVATE_SET = 512;
|
||||
|
||||
public const VISIBILITY_MASK = 1 | 2 | 4;
|
||||
public const VISIBILITY_MASK = self::PUBLIC | self::PROTECTED | self::PRIVATE;
|
||||
|
||||
public const VISIBILITY_SET_MASK = self::PUBLIC_SET | self::PROTECTED_SET | self::PRIVATE_SET;
|
||||
|
||||
private const TO_STRING_MAP = [
|
||||
self::PUBLIC => 'public',
|
||||
self::PROTECTED => 'protected',
|
||||
self::PRIVATE => 'private',
|
||||
self::STATIC => 'static',
|
||||
self::ABSTRACT => 'abstract',
|
||||
self::FINAL => 'final',
|
||||
self::READONLY => 'readonly',
|
||||
self::PUBLIC_SET => 'public(set)',
|
||||
self::PROTECTED_SET => 'protected(set)',
|
||||
self::PRIVATE_SET => 'private(set)',
|
||||
];
|
||||
|
||||
public static function toString(int $modifier): string {
|
||||
if (!isset(self::TO_STRING_MAP[$modifier])) {
|
||||
throw new \InvalidArgumentException("Unknown modifier $modifier");
|
||||
}
|
||||
return self::TO_STRING_MAP[$modifier];
|
||||
}
|
||||
|
||||
private static function isValidModifier(int $modifier): bool {
|
||||
$isPow2 = ($modifier & ($modifier - 1)) == 0 && $modifier != 0;
|
||||
return $isPow2 && $modifier <= self::PRIVATE_SET;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public static function verifyClassModifier(int $a, int $b): void {
|
||||
if ($a & Modifiers::ABSTRACT && $b & Modifiers::ABSTRACT) {
|
||||
throw new Error('Multiple abstract modifiers are not allowed');
|
||||
}
|
||||
|
||||
if ($a & Modifiers::FINAL && $b & Modifiers::FINAL) {
|
||||
throw new Error('Multiple final modifiers are not allowed');
|
||||
}
|
||||
|
||||
if ($a & Modifiers::READONLY && $b & Modifiers::READONLY) {
|
||||
throw new Error('Multiple readonly modifiers are not allowed');
|
||||
assert(self::isValidModifier($b));
|
||||
if (($a & $b) != 0) {
|
||||
throw new Error(
|
||||
'Multiple ' . self::toString($b) . ' modifiers are not allowed');
|
||||
}
|
||||
|
||||
if ($a & 48 && $b & 48) {
|
||||
@@ -42,24 +66,16 @@ final class Modifiers {
|
||||
* @internal
|
||||
*/
|
||||
public static function verifyModifier(int $a, int $b): void {
|
||||
if ($a & Modifiers::VISIBILITY_MASK && $b & Modifiers::VISIBILITY_MASK) {
|
||||
assert(self::isValidModifier($b));
|
||||
if (($a & Modifiers::VISIBILITY_MASK && $b & Modifiers::VISIBILITY_MASK) ||
|
||||
($a & Modifiers::VISIBILITY_SET_MASK && $b & Modifiers::VISIBILITY_SET_MASK)
|
||||
) {
|
||||
throw new Error('Multiple access type modifiers are not allowed');
|
||||
}
|
||||
|
||||
if ($a & Modifiers::ABSTRACT && $b & Modifiers::ABSTRACT) {
|
||||
throw new Error('Multiple abstract modifiers are not allowed');
|
||||
}
|
||||
|
||||
if ($a & Modifiers::STATIC && $b & Modifiers::STATIC) {
|
||||
throw new Error('Multiple static modifiers are not allowed');
|
||||
}
|
||||
|
||||
if ($a & Modifiers::FINAL && $b & Modifiers::FINAL) {
|
||||
throw new Error('Multiple final modifiers are not allowed');
|
||||
}
|
||||
|
||||
if ($a & Modifiers::READONLY && $b & Modifiers::READONLY) {
|
||||
throw new Error('Multiple readonly modifiers are not allowed');
|
||||
if (($a & $b) != 0) {
|
||||
throw new Error(
|
||||
'Multiple ' . self::toString($b) . ' modifiers are not allowed');
|
||||
}
|
||||
|
||||
if ($a & 48 && $b & 48) {
|
||||
|
@@ -185,7 +185,7 @@ class NameContext {
|
||||
// Check for relevant type-specific use statements
|
||||
foreach ($this->origAliases[$type] as $alias => $orig) {
|
||||
if ($type === Stmt\Use_::TYPE_CONSTANT) {
|
||||
// Constants are are complicated-sensitive
|
||||
// Constants are complicated-sensitive
|
||||
$normalizedOrig = $this->normalizeConstName($orig->toString());
|
||||
if ($normalizedOrig === $this->normalizeConstName($name)) {
|
||||
$possibleNames[] = new Name($alias);
|
||||
|
@@ -6,6 +6,7 @@ interface Node {
|
||||
/**
|
||||
* Gets the type of the node.
|
||||
*
|
||||
* @psalm-return non-empty-string
|
||||
* @return string Type of the node
|
||||
*/
|
||||
public function getType(): string;
|
||||
@@ -21,6 +22,7 @@ interface Node {
|
||||
* Gets line the node started in (alias of getStartLine).
|
||||
*
|
||||
* @return int Start line (or -1 if not available)
|
||||
* @phpstan-return -1|positive-int
|
||||
*
|
||||
* @deprecated Use getStartLine() instead
|
||||
*/
|
||||
@@ -32,6 +34,7 @@ interface Node {
|
||||
* Requires the 'startLine' attribute to be enabled in the lexer (enabled by default).
|
||||
*
|
||||
* @return int Start line (or -1 if not available)
|
||||
* @phpstan-return -1|positive-int
|
||||
*/
|
||||
public function getStartLine(): int;
|
||||
|
||||
@@ -41,6 +44,7 @@ interface Node {
|
||||
* Requires the 'endLine' attribute to be enabled in the lexer (enabled by default).
|
||||
*
|
||||
* @return int End line (or -1 if not available)
|
||||
* @phpstan-return -1|positive-int
|
||||
*/
|
||||
public function getEndLine(): int;
|
||||
|
||||
|
@@ -1,3 +1,11 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser\Node\Expr;
|
||||
|
||||
require __DIR__ . '/../ArrayItem.php';
|
||||
|
||||
if (false) {
|
||||
// For classmap-authoritative support.
|
||||
class ArrayItem extends \PhpParser\Node\ArrayItem {
|
||||
}
|
||||
}
|
||||
|
@@ -7,8 +7,10 @@ use PhpParser\Node\Expr;
|
||||
use PhpParser\Node\FunctionLike;
|
||||
|
||||
class ArrowFunction extends Expr implements FunctionLike {
|
||||
/** @var bool Whether the closure is static */
|
||||
public bool $static;
|
||||
|
||||
/** @var bool Whether to return by reference */
|
||||
public bool $byRef;
|
||||
|
||||
/** @var Node\Param[] */
|
||||
@@ -17,6 +19,7 @@ class ArrowFunction extends Expr implements FunctionLike {
|
||||
/** @var null|Node\Identifier|Node\Name|Node\ComplexType */
|
||||
public ?Node $returnType;
|
||||
|
||||
/** @var Expr Expression body */
|
||||
public Expr $expr;
|
||||
/** @var Node\AttributeGroup[] */
|
||||
public array $attrGroups;
|
||||
|
15
lib/PhpParser/Node/Expr/BinaryOp/Pipe.php
Normal file
15
lib/PhpParser/Node/Expr/BinaryOp/Pipe.php
Normal file
@@ -0,0 +1,15 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser\Node\Expr\BinaryOp;
|
||||
|
||||
use PhpParser\Node\Expr\BinaryOp;
|
||||
|
||||
class Pipe extends BinaryOp {
|
||||
public function getOperatorSigil(): string {
|
||||
return '|>';
|
||||
}
|
||||
|
||||
public function getType(): string {
|
||||
return 'Expr_BinaryOp_Pipe';
|
||||
}
|
||||
}
|
@@ -32,4 +32,29 @@ abstract class CallLike extends Expr {
|
||||
assert(!$this->isFirstClassCallable());
|
||||
return $this->getRawArgs();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a specific argument from the raw arguments.
|
||||
*
|
||||
* Returns the named argument that matches the given `$name`, or the
|
||||
* positional (unnamed) argument that exists at the given `$position`,
|
||||
* otherwise, returns `null` for first-class callables or if no match is found.
|
||||
*/
|
||||
public function getArg(string $name, int $position): ?Arg {
|
||||
if ($this->isFirstClassCallable()) {
|
||||
return null;
|
||||
}
|
||||
foreach ($this->getRawArgs() as $i => $arg) {
|
||||
if ($arg->unpack) {
|
||||
continue;
|
||||
}
|
||||
if (
|
||||
($arg->name !== null && $arg->name->toString() === $name)
|
||||
|| ($arg->name === null && $i === $position)
|
||||
) {
|
||||
return $arg;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
11
lib/PhpParser/Node/Expr/Cast/Void_.php
Normal file
11
lib/PhpParser/Node/Expr/Cast/Void_.php
Normal file
@@ -0,0 +1,11 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser\Node\Expr\Cast;
|
||||
|
||||
use PhpParser\Node\Expr\Cast;
|
||||
|
||||
class Void_ extends Cast {
|
||||
public function getType(): string {
|
||||
return 'Expr_Cast_Void';
|
||||
}
|
||||
}
|
@@ -1,3 +1,11 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser\Node\Expr;
|
||||
|
||||
require __DIR__ . '/../ClosureUse.php';
|
||||
|
||||
if (false) {
|
||||
// For classmap-authoritative support.
|
||||
class ClosureUse extends \PhpParser\Node\ClosureUse {
|
||||
}
|
||||
}
|
||||
|
@@ -6,12 +6,15 @@ use PhpParser\Node;
|
||||
use PhpParser\Node\MatchArm;
|
||||
|
||||
class Match_ extends Node\Expr {
|
||||
/** @var Node\Expr Condition */
|
||||
public Node\Expr $cond;
|
||||
/** @var MatchArm[] */
|
||||
public array $arms;
|
||||
|
||||
/**
|
||||
* @param Node\Expr $cond Condition
|
||||
* @param MatchArm[] $arms
|
||||
* @param array<string, mixed> $attributes Additional attributes
|
||||
*/
|
||||
public function __construct(Node\Expr $cond, array $arms = [], array $attributes = []) {
|
||||
$this->attributes = $attributes;
|
||||
|
@@ -8,7 +8,10 @@ use PhpParser\NodeAbstract;
|
||||
* Represents a non-namespaced name. Namespaced names are represented using Name nodes.
|
||||
*/
|
||||
class Identifier extends NodeAbstract {
|
||||
/** @var string Identifier as string */
|
||||
/**
|
||||
* @psalm-var non-empty-string
|
||||
* @var string Identifier as string
|
||||
*/
|
||||
public string $name;
|
||||
|
||||
/** @var array<string, bool> */
|
||||
@@ -25,6 +28,10 @@ class Identifier extends NodeAbstract {
|
||||
* @param array<string, mixed> $attributes Additional attributes
|
||||
*/
|
||||
public function __construct(string $name, array $attributes = []) {
|
||||
if ($name === '') {
|
||||
throw new \InvalidArgumentException('Identifier name cannot be empty');
|
||||
}
|
||||
|
||||
$this->attributes = $attributes;
|
||||
$this->name = $name;
|
||||
}
|
||||
@@ -36,6 +43,7 @@ class Identifier extends NodeAbstract {
|
||||
/**
|
||||
* Get identifier as string.
|
||||
*
|
||||
* @psalm-return non-empty-string
|
||||
* @return string Identifier as string.
|
||||
*/
|
||||
public function toString(): string {
|
||||
@@ -45,6 +53,7 @@ class Identifier extends NodeAbstract {
|
||||
/**
|
||||
* Get lowercased identifier as string.
|
||||
*
|
||||
* @psalm-return non-empty-string&lowercase-string
|
||||
* @return string Lowercased identifier as string
|
||||
*/
|
||||
public function toLowerString(): string {
|
||||
@@ -63,6 +72,7 @@ class Identifier extends NodeAbstract {
|
||||
/**
|
||||
* Get identifier as string.
|
||||
*
|
||||
* @psalm-return non-empty-string
|
||||
* @return string Identifier as string
|
||||
*/
|
||||
public function __toString(): string {
|
||||
|
@@ -8,7 +8,6 @@ use PhpParser\NodeAbstract;
|
||||
class MatchArm extends NodeAbstract {
|
||||
/** @var null|list<Node\Expr> */
|
||||
public ?array $conds;
|
||||
/** @var Node\Expr */
|
||||
public Expr $body;
|
||||
|
||||
/**
|
||||
|
@@ -5,7 +5,10 @@ namespace PhpParser\Node;
|
||||
use PhpParser\NodeAbstract;
|
||||
|
||||
class Name extends NodeAbstract {
|
||||
/** @var string Name as string */
|
||||
/**
|
||||
* @psalm-var non-empty-string
|
||||
* @var string Name as string
|
||||
*/
|
||||
public string $name;
|
||||
|
||||
/** @var array<string, bool> */
|
||||
@@ -33,6 +36,7 @@ class Name extends NodeAbstract {
|
||||
/**
|
||||
* Get parts of name (split by the namespace separator).
|
||||
*
|
||||
* @psalm-return non-empty-list<string>
|
||||
* @return string[] Parts of name
|
||||
*/
|
||||
public function getParts(): array {
|
||||
@@ -103,6 +107,7 @@ class Name extends NodeAbstract {
|
||||
* Returns a string representation of the name itself, without taking the name type into
|
||||
* account (e.g., not including a leading backslash for fully qualified names).
|
||||
*
|
||||
* @psalm-return non-empty-string
|
||||
* @return string String representation
|
||||
*/
|
||||
public function toString(): string {
|
||||
@@ -113,6 +118,7 @@ class Name extends NodeAbstract {
|
||||
* Returns a string representation of the name as it would occur in code (e.g., including
|
||||
* leading backslash for fully qualified names.
|
||||
*
|
||||
* @psalm-return non-empty-string
|
||||
* @return string String representation
|
||||
*/
|
||||
public function toCodeString(): string {
|
||||
@@ -123,6 +129,7 @@ class Name extends NodeAbstract {
|
||||
* Returns lowercased string representation of the name, without taking the name type into
|
||||
* account (e.g., no leading backslash for fully qualified names).
|
||||
*
|
||||
* @psalm-return non-empty-string&lowercase-string
|
||||
* @return string Lowercased string representation
|
||||
*/
|
||||
public function toLowerString(): string {
|
||||
@@ -142,6 +149,7 @@ class Name extends NodeAbstract {
|
||||
* Returns a string representation of the name by imploding the namespace parts with the
|
||||
* namespace separator.
|
||||
*
|
||||
* @psalm-return non-empty-string
|
||||
* @return string String representation
|
||||
*/
|
||||
public function __toString(): string {
|
||||
@@ -237,6 +245,7 @@ class Name extends NodeAbstract {
|
||||
*
|
||||
* @param string|string[]|self $name Name to prepare
|
||||
*
|
||||
* @psalm-return non-empty-string
|
||||
* @return string Prepared name
|
||||
*/
|
||||
private static function prepareName($name): string {
|
||||
|
@@ -17,9 +17,12 @@ class Param extends NodeAbstract {
|
||||
public Expr $var;
|
||||
/** @var null|Expr Default value */
|
||||
public ?Expr $default;
|
||||
/** @var int Optional visibility flags */
|
||||
public int $flags;
|
||||
/** @var AttributeGroup[] PHP attribute groups */
|
||||
public array $attrGroups;
|
||||
/** @var PropertyHook[] Property hooks for promoted properties */
|
||||
public array $hooks;
|
||||
|
||||
/**
|
||||
* Constructs a parameter node.
|
||||
@@ -32,13 +35,15 @@ class Param extends NodeAbstract {
|
||||
* @param array<string, mixed> $attributes Additional attributes
|
||||
* @param int $flags Optional visibility flags
|
||||
* @param list<AttributeGroup> $attrGroups PHP attribute groups
|
||||
* @param PropertyHook[] $hooks Property hooks for promoted properties
|
||||
*/
|
||||
public function __construct(
|
||||
Expr $var, ?Expr $default = null, ?Node $type = null,
|
||||
bool $byRef = false, bool $variadic = false,
|
||||
array $attributes = [],
|
||||
int $flags = 0,
|
||||
array $attrGroups = []
|
||||
array $attrGroups = [],
|
||||
array $hooks = []
|
||||
) {
|
||||
$this->attributes = $attributes;
|
||||
$this->type = $type;
|
||||
@@ -48,10 +53,11 @@ class Param extends NodeAbstract {
|
||||
$this->default = $default;
|
||||
$this->flags = $flags;
|
||||
$this->attrGroups = $attrGroups;
|
||||
$this->hooks = $hooks;
|
||||
}
|
||||
|
||||
public function getSubNodeNames(): array {
|
||||
return ['attrGroups', 'flags', 'type', 'byRef', 'variadic', 'var', 'default'];
|
||||
return ['attrGroups', 'flags', 'type', 'byRef', 'variadic', 'var', 'default', 'hooks'];
|
||||
}
|
||||
|
||||
public function getType(): string {
|
||||
@@ -62,11 +68,20 @@ class Param extends NodeAbstract {
|
||||
* Whether this parameter uses constructor property promotion.
|
||||
*/
|
||||
public function isPromoted(): bool {
|
||||
return $this->flags !== 0;
|
||||
return $this->flags !== 0 || $this->hooks !== [];
|
||||
}
|
||||
|
||||
public function isPublic(): bool {
|
||||
return (bool) ($this->flags & Modifiers::PUBLIC);
|
||||
$public = (bool) ($this->flags & Modifiers::PUBLIC);
|
||||
if ($public) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($this->hooks === []) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ($this->flags & Modifiers::VISIBILITY_MASK) === 0;
|
||||
}
|
||||
|
||||
public function isProtected(): bool {
|
||||
@@ -80,4 +95,25 @@ class Param extends NodeAbstract {
|
||||
public function isReadonly(): bool {
|
||||
return (bool) ($this->flags & Modifiers::READONLY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the promoted property has explicit public(set) visibility.
|
||||
*/
|
||||
public function isPublicSet(): bool {
|
||||
return (bool) ($this->flags & Modifiers::PUBLIC_SET);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the promoted property has explicit protected(set) visibility.
|
||||
*/
|
||||
public function isProtectedSet(): bool {
|
||||
return (bool) ($this->flags & Modifiers::PROTECTED_SET);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the promoted property has explicit private(set) visibility.
|
||||
*/
|
||||
public function isPrivateSet(): bool {
|
||||
return (bool) ($this->flags & Modifiers::PRIVATE_SET);
|
||||
}
|
||||
}
|
||||
|
105
lib/PhpParser/Node/PropertyHook.php
Normal file
105
lib/PhpParser/Node/PropertyHook.php
Normal file
@@ -0,0 +1,105 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser\Node;
|
||||
|
||||
use PhpParser\Modifiers;
|
||||
use PhpParser\Node\Expr\Assign;
|
||||
use PhpParser\Node\Expr\PropertyFetch;
|
||||
use PhpParser\Node\Expr\Variable;
|
||||
use PhpParser\Node\Stmt\Expression;
|
||||
use PhpParser\Node\Stmt\Return_;
|
||||
use PhpParser\NodeAbstract;
|
||||
|
||||
class PropertyHook extends NodeAbstract implements FunctionLike {
|
||||
/** @var AttributeGroup[] PHP attribute groups */
|
||||
public array $attrGroups;
|
||||
/** @var int Modifiers */
|
||||
public int $flags;
|
||||
/** @var bool Whether hook returns by reference */
|
||||
public bool $byRef;
|
||||
/** @var Identifier Hook name */
|
||||
public Identifier $name;
|
||||
/** @var Param[] Parameters */
|
||||
public array $params;
|
||||
/** @var null|Expr|Stmt[] Hook body */
|
||||
public $body;
|
||||
|
||||
/**
|
||||
* Constructs a property hook node.
|
||||
*
|
||||
* @param string|Identifier $name Hook name
|
||||
* @param null|Expr|Stmt[] $body Hook body
|
||||
* @param array{
|
||||
* flags?: int,
|
||||
* byRef?: bool,
|
||||
* params?: Param[],
|
||||
* attrGroups?: AttributeGroup[],
|
||||
* } $subNodes Array of the following optional subnodes:
|
||||
* 'flags => 0 : Flags
|
||||
* 'byRef' => false : Whether hook returns by reference
|
||||
* 'params' => array(): Parameters
|
||||
* 'attrGroups' => array(): PHP attribute groups
|
||||
* @param array<string, mixed> $attributes Additional attributes
|
||||
*/
|
||||
public function __construct($name, $body, array $subNodes = [], array $attributes = []) {
|
||||
$this->attributes = $attributes;
|
||||
$this->name = \is_string($name) ? new Identifier($name) : $name;
|
||||
$this->body = $body;
|
||||
$this->flags = $subNodes['flags'] ?? 0;
|
||||
$this->byRef = $subNodes['byRef'] ?? false;
|
||||
$this->params = $subNodes['params'] ?? [];
|
||||
$this->attrGroups = $subNodes['attrGroups'] ?? [];
|
||||
}
|
||||
|
||||
public function returnsByRef(): bool {
|
||||
return $this->byRef;
|
||||
}
|
||||
|
||||
public function getParams(): array {
|
||||
return $this->params;
|
||||
}
|
||||
|
||||
public function getReturnType() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the property hook is final.
|
||||
*/
|
||||
public function isFinal(): bool {
|
||||
return (bool) ($this->flags & Modifiers::FINAL);
|
||||
}
|
||||
|
||||
public function getStmts(): ?array {
|
||||
if ($this->body instanceof Expr) {
|
||||
$name = $this->name->toLowerString();
|
||||
if ($name === 'get') {
|
||||
return [new Return_($this->body)];
|
||||
}
|
||||
if ($name === 'set') {
|
||||
if (!$this->hasAttribute('propertyName')) {
|
||||
throw new \LogicException(
|
||||
'Can only use getStmts() on a "set" hook if the "propertyName" attribute is set');
|
||||
}
|
||||
|
||||
$propName = $this->getAttribute('propertyName');
|
||||
$prop = new PropertyFetch(new Variable('this'), (string) $propName);
|
||||
return [new Expression(new Assign($prop, $this->body))];
|
||||
}
|
||||
throw new \LogicException('Unknown property hook "' . $name . '"');
|
||||
}
|
||||
return $this->body;
|
||||
}
|
||||
|
||||
public function getAttrGroups(): array {
|
||||
return $this->attrGroups;
|
||||
}
|
||||
|
||||
public function getType(): string {
|
||||
return 'PropertyHook';
|
||||
}
|
||||
|
||||
public function getSubNodeNames(): array {
|
||||
return ['attrGroups', 'flags', 'byRef', 'name', 'params', 'body'];
|
||||
}
|
||||
}
|
@@ -3,8 +3,9 @@
|
||||
namespace PhpParser\Node;
|
||||
|
||||
use PhpParser\Node;
|
||||
use PhpParser\NodeAbstract;
|
||||
|
||||
class PropertyItem extends Node\Stmt {
|
||||
class PropertyItem extends NodeAbstract {
|
||||
/** @var Node\VarLikeIdentifier Name */
|
||||
public VarLikeIdentifier $name;
|
||||
/** @var null|Node\Expr Default */
|
||||
|
@@ -1,3 +1,11 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser\Node\Scalar;
|
||||
|
||||
require __DIR__ . '/Float_.php';
|
||||
|
||||
if (false) {
|
||||
// For classmap-authoritative support.
|
||||
class DNumber extends Float_ {
|
||||
}
|
||||
}
|
||||
|
@@ -1,3 +1,11 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser\Node\Scalar;
|
||||
|
||||
require __DIR__ . '/InterpolatedString.php';
|
||||
|
||||
if (false) {
|
||||
// For classmap-authoritative support.
|
||||
class Encapsed extends InterpolatedString {
|
||||
}
|
||||
}
|
||||
|
@@ -1,3 +1,13 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser\Node\Scalar;
|
||||
|
||||
use PhpParser\Node\InterpolatedStringPart;
|
||||
|
||||
require __DIR__ . '/../InterpolatedStringPart.php';
|
||||
|
||||
if (false) {
|
||||
// For classmap-authoritative support.
|
||||
class EncapsedStringPart extends InterpolatedStringPart {
|
||||
}
|
||||
}
|
||||
|
@@ -1,3 +1,11 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser\Node\Scalar;
|
||||
|
||||
require __DIR__ . '/Int_.php';
|
||||
|
||||
if (false) {
|
||||
// For classmap-authoritative support.
|
||||
class LNumber extends Int_ {
|
||||
}
|
||||
}
|
||||
|
15
lib/PhpParser/Node/Scalar/MagicConst/Property.php
Normal file
15
lib/PhpParser/Node/Scalar/MagicConst/Property.php
Normal file
@@ -0,0 +1,15 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser\Node\Scalar\MagicConst;
|
||||
|
||||
use PhpParser\Node\Scalar\MagicConst;
|
||||
|
||||
class Property extends MagicConst {
|
||||
public function getName(): string {
|
||||
return '__PROPERTY__';
|
||||
}
|
||||
|
||||
public function getType(): string {
|
||||
return 'Scalar_MagicConst_Property';
|
||||
}
|
||||
}
|
29
lib/PhpParser/Node/Stmt/Block.php
Normal file
29
lib/PhpParser/Node/Stmt/Block.php
Normal file
@@ -0,0 +1,29 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser\Node\Stmt;
|
||||
|
||||
use PhpParser\Node\Stmt;
|
||||
|
||||
class Block extends Stmt {
|
||||
/** @var Stmt[] Statements */
|
||||
public array $stmts;
|
||||
|
||||
/**
|
||||
* A block of statements.
|
||||
*
|
||||
* @param Stmt[] $stmts Statements
|
||||
* @param array<string, mixed> $attributes Additional attributes
|
||||
*/
|
||||
public function __construct(array $stmts, array $attributes = []) {
|
||||
$this->attributes = $attributes;
|
||||
$this->stmts = $stmts;
|
||||
}
|
||||
|
||||
public function getType(): string {
|
||||
return 'Stmt_Block';
|
||||
}
|
||||
|
||||
public function getSubNodeNames(): array {
|
||||
return ['stmts'];
|
||||
}
|
||||
}
|
@@ -17,7 +17,7 @@ abstract class ClassLike extends Node\Stmt {
|
||||
public ?Node\Name $namespacedName;
|
||||
|
||||
/**
|
||||
* @return TraitUse[]
|
||||
* @return list<TraitUse>
|
||||
*/
|
||||
public function getTraitUses(): array {
|
||||
$traitUses = [];
|
||||
@@ -30,7 +30,7 @@ abstract class ClassLike extends Node\Stmt {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ClassConst[]
|
||||
* @return list<ClassConst>
|
||||
*/
|
||||
public function getConstants(): array {
|
||||
$constants = [];
|
||||
@@ -43,7 +43,7 @@ abstract class ClassLike extends Node\Stmt {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Property[]
|
||||
* @return list<Property>
|
||||
*/
|
||||
public function getProperties(): array {
|
||||
$properties = [];
|
||||
@@ -78,7 +78,7 @@ abstract class ClassLike extends Node\Stmt {
|
||||
/**
|
||||
* Gets all methods defined directly in this class/interface/trait
|
||||
*
|
||||
* @return ClassMethod[]
|
||||
* @return list<ClassMethod>
|
||||
*/
|
||||
public function getMethods(): array {
|
||||
$methods = [];
|
||||
|
@@ -7,20 +7,28 @@ use PhpParser\Node;
|
||||
class Const_ extends Node\Stmt {
|
||||
/** @var Node\Const_[] Constant declarations */
|
||||
public array $consts;
|
||||
/** @var Node\AttributeGroup[] PHP attribute groups */
|
||||
public array $attrGroups;
|
||||
|
||||
/**
|
||||
* Constructs a const list node.
|
||||
*
|
||||
* @param Node\Const_[] $consts Constant declarations
|
||||
* @param array<string, mixed> $attributes Additional attributes
|
||||
* @param list<Node\AttributeGroup> $attrGroups PHP attribute groups
|
||||
*/
|
||||
public function __construct(array $consts, array $attributes = []) {
|
||||
public function __construct(
|
||||
array $consts,
|
||||
array $attributes = [],
|
||||
array $attrGroups = []
|
||||
) {
|
||||
$this->attributes = $attributes;
|
||||
$this->attrGroups = $attrGroups;
|
||||
$this->consts = $consts;
|
||||
}
|
||||
|
||||
public function getSubNodeNames(): array {
|
||||
return ['consts'];
|
||||
return ['attrGroups', 'consts'];
|
||||
}
|
||||
|
||||
public function getType(): string {
|
||||
|
@@ -1,3 +1,13 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser\Node\Stmt;
|
||||
|
||||
use PhpParser\Node\DeclareItem;
|
||||
|
||||
require __DIR__ . '/../DeclareItem.php';
|
||||
|
||||
if (false) {
|
||||
// For classmap-authoritative support.
|
||||
class DeclareDeclare extends DeclareItem {
|
||||
}
|
||||
}
|
||||
|
@@ -18,6 +18,8 @@ class Property extends Node\Stmt {
|
||||
public ?Node $type;
|
||||
/** @var Node\AttributeGroup[] PHP attribute groups */
|
||||
public array $attrGroups;
|
||||
/** @var Node\PropertyHook[] Property hooks */
|
||||
public array $hooks;
|
||||
|
||||
/**
|
||||
* Constructs a class property list node.
|
||||
@@ -27,17 +29,19 @@ class Property extends Node\Stmt {
|
||||
* @param array<string, mixed> $attributes Additional attributes
|
||||
* @param null|Identifier|Name|ComplexType $type Type declaration
|
||||
* @param Node\AttributeGroup[] $attrGroups PHP attribute groups
|
||||
* @param Node\PropertyHook[] $hooks Property hooks
|
||||
*/
|
||||
public function __construct(int $flags, array $props, array $attributes = [], ?Node $type = null, array $attrGroups = []) {
|
||||
public function __construct(int $flags, array $props, array $attributes = [], ?Node $type = null, array $attrGroups = [], array $hooks = []) {
|
||||
$this->attributes = $attributes;
|
||||
$this->flags = $flags;
|
||||
$this->props = $props;
|
||||
$this->type = $type;
|
||||
$this->attrGroups = $attrGroups;
|
||||
$this->hooks = $hooks;
|
||||
}
|
||||
|
||||
public function getSubNodeNames(): array {
|
||||
return ['attrGroups', 'flags', 'type', 'props'];
|
||||
return ['attrGroups', 'flags', 'type', 'props', 'hooks'];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -76,6 +80,41 @@ class Property extends Node\Stmt {
|
||||
return (bool) ($this->flags & Modifiers::READONLY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the property is abstract.
|
||||
*/
|
||||
public function isAbstract(): bool {
|
||||
return (bool) ($this->flags & Modifiers::ABSTRACT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the property is final.
|
||||
*/
|
||||
public function isFinal(): bool {
|
||||
return (bool) ($this->flags & Modifiers::FINAL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the property has explicit public(set) visibility.
|
||||
*/
|
||||
public function isPublicSet(): bool {
|
||||
return (bool) ($this->flags & Modifiers::PUBLIC_SET);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the property has explicit protected(set) visibility.
|
||||
*/
|
||||
public function isProtectedSet(): bool {
|
||||
return (bool) ($this->flags & Modifiers::PROTECTED_SET);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the property has explicit private(set) visibility.
|
||||
*/
|
||||
public function isPrivateSet(): bool {
|
||||
return (bool) ($this->flags & Modifiers::PRIVATE_SET);
|
||||
}
|
||||
|
||||
public function getType(): string {
|
||||
return 'Stmt_Property';
|
||||
}
|
||||
|
@@ -1,3 +1,13 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser\Node\Stmt;
|
||||
|
||||
use PhpParser\Node\PropertyItem;
|
||||
|
||||
require __DIR__ . '/../PropertyItem.php';
|
||||
|
||||
if (false) {
|
||||
// For classmap-authoritative support.
|
||||
class PropertyProperty extends PropertyItem {
|
||||
}
|
||||
}
|
||||
|
@@ -1,3 +1,11 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser\Node\Stmt;
|
||||
|
||||
require __DIR__ . '/../StaticVar.php';
|
||||
|
||||
if (false) {
|
||||
// For classmap-authoritative support.
|
||||
class StaticVar extends \PhpParser\Node\StaticVar {
|
||||
}
|
||||
}
|
||||
|
@@ -1,29 +0,0 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser\Node\Stmt;
|
||||
|
||||
use PhpParser\Node;
|
||||
|
||||
class Throw_ extends Node\Stmt {
|
||||
/** @var Node\Expr Expression */
|
||||
public Node\Expr $expr;
|
||||
|
||||
/**
|
||||
* Constructs a legacy throw statement node.
|
||||
*
|
||||
* @param Node\Expr $expr Expression
|
||||
* @param array<string, mixed> $attributes Additional attributes
|
||||
*/
|
||||
public function __construct(Node\Expr $expr, array $attributes = []) {
|
||||
$this->attributes = $attributes;
|
||||
$this->expr = $expr;
|
||||
}
|
||||
|
||||
public function getSubNodeNames(): array {
|
||||
return ['expr'];
|
||||
}
|
||||
|
||||
public function getType(): string {
|
||||
return 'Stmt_Throw';
|
||||
}
|
||||
}
|
@@ -1,3 +1,13 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser\Node\Stmt;
|
||||
|
||||
use PhpParser\Node\UseItem;
|
||||
|
||||
require __DIR__ . '/../UseItem.php';
|
||||
|
||||
if (false) {
|
||||
// For classmap-authoritative support.
|
||||
class UseUse extends UseItem {
|
||||
}
|
||||
}
|
||||
|
@@ -3,9 +3,10 @@
|
||||
namespace PhpParser\Node;
|
||||
|
||||
use PhpParser\Node;
|
||||
use PhpParser\NodeAbstract;
|
||||
use PhpParser\Node\Stmt\Use_;
|
||||
|
||||
class UseItem extends Node\Stmt {
|
||||
class UseItem extends NodeAbstract {
|
||||
/**
|
||||
* @var Use_::TYPE_* One of the Stmt\Use_::TYPE_* constants. Will only differ from TYPE_UNKNOWN for mixed group uses
|
||||
*/
|
||||
|
@@ -19,6 +19,7 @@ abstract class NodeAbstract implements Node, \JsonSerializable {
|
||||
* Gets line the node started in (alias of getStartLine).
|
||||
*
|
||||
* @return int Start line (or -1 if not available)
|
||||
* @phpstan-return -1|positive-int
|
||||
*/
|
||||
public function getLine(): int {
|
||||
return $this->attributes['startLine'] ?? -1;
|
||||
@@ -30,6 +31,7 @@ abstract class NodeAbstract implements Node, \JsonSerializable {
|
||||
* Requires the 'startLine' attribute to be enabled in the lexer (enabled by default).
|
||||
*
|
||||
* @return int Start line (or -1 if not available)
|
||||
* @phpstan-return -1|positive-int
|
||||
*/
|
||||
public function getStartLine(): int {
|
||||
return $this->attributes['startLine'] ?? -1;
|
||||
@@ -41,6 +43,7 @@ abstract class NodeAbstract implements Node, \JsonSerializable {
|
||||
* Requires the 'endLine' attribute to be enabled in the lexer (enabled by default).
|
||||
*
|
||||
* @return int End line (or -1 if not available)
|
||||
* @phpstan-return -1|positive-int
|
||||
*/
|
||||
public function getEndLine(): int {
|
||||
return $this->attributes['endLine'] ?? -1;
|
||||
|
@@ -2,7 +2,12 @@
|
||||
|
||||
namespace PhpParser;
|
||||
|
||||
use PhpParser\Node\Expr\Array_;
|
||||
use PhpParser\Node\Expr\Include_;
|
||||
use PhpParser\Node\Expr\List_;
|
||||
use PhpParser\Node\Scalar\Int_;
|
||||
use PhpParser\Node\Scalar\InterpolatedString;
|
||||
use PhpParser\Node\Scalar\String_;
|
||||
use PhpParser\Node\Stmt\GroupUse;
|
||||
use PhpParser\Node\Stmt\Use_;
|
||||
use PhpParser\Node\UseItem;
|
||||
@@ -10,7 +15,20 @@ use PhpParser\Node\UseItem;
|
||||
class NodeDumper {
|
||||
private bool $dumpComments;
|
||||
private bool $dumpPositions;
|
||||
private bool $dumpOtherAttributes;
|
||||
private ?string $code;
|
||||
private string $res;
|
||||
private string $nl;
|
||||
|
||||
private const IGNORE_ATTRIBUTES = [
|
||||
'comments' => true,
|
||||
'startLine' => true,
|
||||
'endLine' => true,
|
||||
'startFilePos' => true,
|
||||
'endFilePos' => true,
|
||||
'startTokenPos' => true,
|
||||
'endTokenPos' => true,
|
||||
];
|
||||
|
||||
/**
|
||||
* Constructs a NodeDumper.
|
||||
@@ -19,12 +37,14 @@ class NodeDumper {
|
||||
* * bool dumpComments: Whether comments should be dumped.
|
||||
* * bool dumpPositions: Whether line/offset information should be dumped. To dump offset
|
||||
* information, the code needs to be passed to dump().
|
||||
* * bool dumpOtherAttributes: Whether non-comment, non-position attributes should be dumped.
|
||||
*
|
||||
* @param array $options Options (see description)
|
||||
*/
|
||||
public function __construct(array $options = []) {
|
||||
$this->dumpComments = !empty($options['dumpComments']);
|
||||
$this->dumpPositions = !empty($options['dumpPositions']);
|
||||
$this->dumpOtherAttributes = !empty($options['dumpOtherAttributes']);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -39,72 +59,107 @@ class NodeDumper {
|
||||
*/
|
||||
public function dump($node, ?string $code = null): string {
|
||||
$this->code = $code;
|
||||
return $this->dumpRecursive($node);
|
||||
$this->res = '';
|
||||
$this->nl = "\n";
|
||||
$this->dumpRecursive($node, false);
|
||||
return $this->res;
|
||||
}
|
||||
|
||||
/** @param Node|Comment|array $node */
|
||||
protected function dumpRecursive($node): string {
|
||||
/** @param mixed $node */
|
||||
protected function dumpRecursive($node, bool $indent = true): void {
|
||||
if ($indent) {
|
||||
$this->nl .= " ";
|
||||
}
|
||||
if ($node instanceof Node) {
|
||||
$r = $node->getType();
|
||||
$this->res .= $node->getType();
|
||||
if ($this->dumpPositions && null !== $p = $this->dumpPosition($node)) {
|
||||
$r .= $p;
|
||||
$this->res .= $p;
|
||||
}
|
||||
$r .= '(';
|
||||
$this->res .= '(';
|
||||
|
||||
foreach ($node->getSubNodeNames() as $key) {
|
||||
$r .= "\n " . $key . ': ';
|
||||
$this->res .= "$this->nl " . $key . ': ';
|
||||
|
||||
$value = $node->$key;
|
||||
if (null === $value) {
|
||||
$r .= 'null';
|
||||
} elseif (false === $value) {
|
||||
$r .= 'false';
|
||||
} elseif (true === $value) {
|
||||
$r .= 'true';
|
||||
} elseif (is_scalar($value)) {
|
||||
if (\is_int($value)) {
|
||||
if ('flags' === $key || 'newModifier' === $key) {
|
||||
$r .= $this->dumpFlags($value);
|
||||
} elseif ('type' === $key && $node instanceof Include_) {
|
||||
$r .= $this->dumpIncludeType($value);
|
||||
} elseif ('type' === $key
|
||||
&& ($node instanceof Use_ || $node instanceof UseItem || $node instanceof GroupUse)) {
|
||||
$r .= $this->dumpUseType($value);
|
||||
} else {
|
||||
$r .= $value;
|
||||
$this->res .= $this->dumpFlags($value);
|
||||
continue;
|
||||
}
|
||||
if ('type' === $key && $node instanceof Include_) {
|
||||
$this->res .= $this->dumpIncludeType($value);
|
||||
continue;
|
||||
}
|
||||
if ('type' === $key
|
||||
&& ($node instanceof Use_ || $node instanceof UseItem || $node instanceof GroupUse)) {
|
||||
$this->res .= $this->dumpUseType($value);
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
$r .= str_replace("\n", "\n ", $this->dumpRecursive($value));
|
||||
}
|
||||
$this->dumpRecursive($value);
|
||||
}
|
||||
|
||||
if ($this->dumpComments && $comments = $node->getComments()) {
|
||||
$r .= "\n comments: " . str_replace("\n", "\n ", $this->dumpRecursive($comments));
|
||||
$this->res .= "$this->nl comments: ";
|
||||
$this->dumpRecursive($comments);
|
||||
}
|
||||
} elseif (is_array($node)) {
|
||||
$r = 'array(';
|
||||
|
||||
foreach ($node as $key => $value) {
|
||||
$r .= "\n " . $key . ': ';
|
||||
if ($this->dumpOtherAttributes) {
|
||||
foreach ($node->getAttributes() as $key => $value) {
|
||||
if (isset(self::IGNORE_ATTRIBUTES[$key])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (null === $value) {
|
||||
$r .= 'null';
|
||||
} elseif (false === $value) {
|
||||
$r .= 'false';
|
||||
} elseif (true === $value) {
|
||||
$r .= 'true';
|
||||
} elseif (is_scalar($value)) {
|
||||
$r .= $value;
|
||||
} else {
|
||||
$r .= str_replace("\n", "\n ", $this->dumpRecursive($value));
|
||||
$this->res .= "$this->nl $key: ";
|
||||
if (\is_int($value)) {
|
||||
if ('kind' === $key) {
|
||||
if ($node instanceof Int_) {
|
||||
$this->res .= $this->dumpIntKind($value);
|
||||
continue;
|
||||
}
|
||||
if ($node instanceof String_ || $node instanceof InterpolatedString) {
|
||||
$this->res .= $this->dumpStringKind($value);
|
||||
continue;
|
||||
}
|
||||
if ($node instanceof Array_) {
|
||||
$this->res .= $this->dumpArrayKind($value);
|
||||
continue;
|
||||
}
|
||||
if ($node instanceof List_) {
|
||||
$this->res .= $this->dumpListKind($value);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
$this->dumpRecursive($value);
|
||||
}
|
||||
}
|
||||
$this->res .= "$this->nl)";
|
||||
} elseif (\is_array($node)) {
|
||||
$this->res .= 'array(';
|
||||
foreach ($node as $key => $value) {
|
||||
$this->res .= "$this->nl " . $key . ': ';
|
||||
$this->dumpRecursive($value);
|
||||
}
|
||||
$this->res .= "$this->nl)";
|
||||
} elseif ($node instanceof Comment) {
|
||||
return $node->getReformattedText();
|
||||
$this->res .= \str_replace("\n", $this->nl, $node->getReformattedText());
|
||||
} elseif (\is_string($node)) {
|
||||
$this->res .= \str_replace("\n", $this->nl, $node);
|
||||
} elseif (\is_int($node) || \is_float($node)) {
|
||||
$this->res .= $node;
|
||||
} elseif (null === $node) {
|
||||
$this->res .= 'null';
|
||||
} elseif (false === $node) {
|
||||
$this->res .= 'false';
|
||||
} elseif (true === $node) {
|
||||
$this->res .= 'true';
|
||||
} else {
|
||||
throw new \InvalidArgumentException('Can only dump nodes and arrays.');
|
||||
}
|
||||
|
||||
return $r . "\n)";
|
||||
if ($indent) {
|
||||
$this->nl = \substr($this->nl, 0, -4);
|
||||
}
|
||||
}
|
||||
|
||||
protected function dumpFlags(int $flags): string {
|
||||
@@ -130,6 +185,15 @@ class NodeDumper {
|
||||
if ($flags & Modifiers::READONLY) {
|
||||
$strs[] = 'READONLY';
|
||||
}
|
||||
if ($flags & Modifiers::PUBLIC_SET) {
|
||||
$strs[] = 'PUBLIC_SET';
|
||||
}
|
||||
if ($flags & Modifiers::PROTECTED_SET) {
|
||||
$strs[] = 'PROTECTED_SET';
|
||||
}
|
||||
if ($flags & Modifiers::PRIVATE_SET) {
|
||||
$strs[] = 'PRIVATE_SET';
|
||||
}
|
||||
|
||||
if ($strs) {
|
||||
return implode(' | ', $strs) . ' (' . $flags . ')';
|
||||
@@ -138,32 +202,62 @@ class NodeDumper {
|
||||
}
|
||||
}
|
||||
|
||||
protected function dumpIncludeType(int $type): string {
|
||||
$map = [
|
||||
/** @param array<int, string> $map */
|
||||
private function dumpEnum(int $value, array $map): string {
|
||||
if (!isset($map[$value])) {
|
||||
return (string) $value;
|
||||
}
|
||||
return $map[$value] . ' (' . $value . ')';
|
||||
}
|
||||
|
||||
private function dumpIncludeType(int $type): string {
|
||||
return $this->dumpEnum($type, [
|
||||
Include_::TYPE_INCLUDE => 'TYPE_INCLUDE',
|
||||
Include_::TYPE_INCLUDE_ONCE => 'TYPE_INCLUDE_ONCE',
|
||||
Include_::TYPE_REQUIRE => 'TYPE_REQUIRE',
|
||||
Include_::TYPE_REQUIRE_ONCE => 'TYPE_REQUIRE_ONCE',
|
||||
];
|
||||
|
||||
if (!isset($map[$type])) {
|
||||
return (string) $type;
|
||||
}
|
||||
return $map[$type] . ' (' . $type . ')';
|
||||
]);
|
||||
}
|
||||
|
||||
protected function dumpUseType(int $type): string {
|
||||
$map = [
|
||||
private function dumpUseType(int $type): string {
|
||||
return $this->dumpEnum($type, [
|
||||
Use_::TYPE_UNKNOWN => 'TYPE_UNKNOWN',
|
||||
Use_::TYPE_NORMAL => 'TYPE_NORMAL',
|
||||
Use_::TYPE_FUNCTION => 'TYPE_FUNCTION',
|
||||
Use_::TYPE_CONSTANT => 'TYPE_CONSTANT',
|
||||
];
|
||||
]);
|
||||
}
|
||||
|
||||
if (!isset($map[$type])) {
|
||||
return (string) $type;
|
||||
}
|
||||
return $map[$type] . ' (' . $type . ')';
|
||||
private function dumpIntKind(int $kind): string {
|
||||
return $this->dumpEnum($kind, [
|
||||
Int_::KIND_BIN => 'KIND_BIN',
|
||||
Int_::KIND_OCT => 'KIND_OCT',
|
||||
Int_::KIND_DEC => 'KIND_DEC',
|
||||
Int_::KIND_HEX => 'KIND_HEX',
|
||||
]);
|
||||
}
|
||||
|
||||
private function dumpStringKind(int $kind): string {
|
||||
return $this->dumpEnum($kind, [
|
||||
String_::KIND_SINGLE_QUOTED => 'KIND_SINGLE_QUOTED',
|
||||
String_::KIND_DOUBLE_QUOTED => 'KIND_DOUBLE_QUOTED',
|
||||
String_::KIND_HEREDOC => 'KIND_HEREDOC',
|
||||
String_::KIND_NOWDOC => 'KIND_NOWDOC',
|
||||
]);
|
||||
}
|
||||
|
||||
private function dumpArrayKind(int $kind): string {
|
||||
return $this->dumpEnum($kind, [
|
||||
Array_::KIND_LONG => 'KIND_LONG',
|
||||
Array_::KIND_SHORT => 'KIND_SHORT',
|
||||
]);
|
||||
}
|
||||
|
||||
private function dumpListKind(int $kind): string {
|
||||
return $this->dumpEnum($kind, [
|
||||
List_::KIND_LIST => 'KIND_LIST',
|
||||
List_::KIND_ARRAY => 'KIND_ARRAY',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -15,6 +15,10 @@ class NodeFinder {
|
||||
* @return Node[] Found nodes satisfying the filter callback
|
||||
*/
|
||||
public function find($nodes, callable $filter): array {
|
||||
if ($nodes === []) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (!is_array($nodes)) {
|
||||
$nodes = [$nodes];
|
||||
}
|
||||
@@ -52,6 +56,10 @@ class NodeFinder {
|
||||
* @return null|Node Found node (or null if none found)
|
||||
*/
|
||||
public function findFirst($nodes, callable $filter): ?Node {
|
||||
if ($nodes === []) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!is_array($nodes)) {
|
||||
$nodes = [$nodes];
|
||||
}
|
||||
|
@@ -34,7 +34,7 @@ class NodeTraverser implements NodeTraverserInterface {
|
||||
*
|
||||
* @param NodeVisitor ...$visitors Node visitors
|
||||
*/
|
||||
public function __construct(NodeVisitor... $visitors) {
|
||||
public function __construct(NodeVisitor ...$visitors) {
|
||||
$this->visitors = $visitors;
|
||||
}
|
||||
|
||||
@@ -99,66 +99,72 @@ class NodeTraverser implements NodeTraverserInterface {
|
||||
if ($this->stopTraversal) {
|
||||
break;
|
||||
}
|
||||
} elseif ($subNode instanceof Node) {
|
||||
$traverseChildren = true;
|
||||
$visitorIndex = -1;
|
||||
|
||||
foreach ($this->visitors as $visitorIndex => $visitor) {
|
||||
$return = $visitor->enterNode($subNode);
|
||||
if (null !== $return) {
|
||||
if ($return instanceof Node) {
|
||||
$this->ensureReplacementReasonable($subNode, $return);
|
||||
$subNode = $node->$name = $return;
|
||||
} elseif (NodeVisitor::DONT_TRAVERSE_CHILDREN === $return) {
|
||||
$traverseChildren = false;
|
||||
} elseif (NodeVisitor::DONT_TRAVERSE_CURRENT_AND_CHILDREN === $return) {
|
||||
$traverseChildren = false;
|
||||
break;
|
||||
} elseif (NodeVisitor::STOP_TRAVERSAL === $return) {
|
||||
$this->stopTraversal = true;
|
||||
break 2;
|
||||
} elseif (NodeVisitor::REPLACE_WITH_NULL === $return) {
|
||||
$node->$name = null;
|
||||
continue 2;
|
||||
} else {
|
||||
throw new \LogicException(
|
||||
'enterNode() returned invalid value of type ' . gettype($return)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($traverseChildren) {
|
||||
$this->traverseNode($subNode);
|
||||
if ($this->stopTraversal) {
|
||||
if (!$subNode instanceof Node) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$traverseChildren = true;
|
||||
$visitorIndex = -1;
|
||||
|
||||
foreach ($this->visitors as $visitorIndex => $visitor) {
|
||||
$return = $visitor->enterNode($subNode);
|
||||
if (null !== $return) {
|
||||
if ($return instanceof Node) {
|
||||
$this->ensureReplacementReasonable($subNode, $return);
|
||||
$subNode = $node->$name = $return;
|
||||
} elseif (NodeVisitor::DONT_TRAVERSE_CHILDREN === $return) {
|
||||
$traverseChildren = false;
|
||||
} elseif (NodeVisitor::DONT_TRAVERSE_CURRENT_AND_CHILDREN === $return) {
|
||||
$traverseChildren = false;
|
||||
break;
|
||||
} elseif (NodeVisitor::STOP_TRAVERSAL === $return) {
|
||||
$this->stopTraversal = true;
|
||||
break 2;
|
||||
} elseif (NodeVisitor::REPLACE_WITH_NULL === $return) {
|
||||
$node->$name = null;
|
||||
continue 2;
|
||||
} else {
|
||||
throw new \LogicException(
|
||||
'enterNode() returned invalid value of type ' . gettype($return)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (; $visitorIndex >= 0; --$visitorIndex) {
|
||||
$visitor = $this->visitors[$visitorIndex];
|
||||
$return = $visitor->leaveNode($subNode);
|
||||
if ($traverseChildren) {
|
||||
$this->traverseNode($subNode);
|
||||
if ($this->stopTraversal) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (null !== $return) {
|
||||
if ($return instanceof Node) {
|
||||
$this->ensureReplacementReasonable($subNode, $return);
|
||||
$subNode = $node->$name = $return;
|
||||
} elseif (NodeVisitor::STOP_TRAVERSAL === $return) {
|
||||
$this->stopTraversal = true;
|
||||
break 2;
|
||||
} elseif (NodeVisitor::REPLACE_WITH_NULL === $return) {
|
||||
$node->$name = null;
|
||||
break;
|
||||
} elseif (\is_array($return)) {
|
||||
throw new \LogicException(
|
||||
'leaveNode() may only return an array ' .
|
||||
'if the parent structure is an array'
|
||||
);
|
||||
} else {
|
||||
throw new \LogicException(
|
||||
'leaveNode() returned invalid value of type ' . gettype($return)
|
||||
);
|
||||
}
|
||||
for (; $visitorIndex >= 0; --$visitorIndex) {
|
||||
$visitor = $this->visitors[$visitorIndex];
|
||||
$return = $visitor->leaveNode($subNode);
|
||||
|
||||
if (null !== $return) {
|
||||
if ($return instanceof Node) {
|
||||
$this->ensureReplacementReasonable($subNode, $return);
|
||||
$subNode = $node->$name = $return;
|
||||
} elseif (NodeVisitor::STOP_TRAVERSAL === $return) {
|
||||
$this->stopTraversal = true;
|
||||
break 2;
|
||||
} elseif (NodeVisitor::REPLACE_WITH_NULL === $return) {
|
||||
$node->$name = null;
|
||||
break;
|
||||
} elseif (\is_array($return)) {
|
||||
throw new \LogicException(
|
||||
'leaveNode() may only return an array ' .
|
||||
'if the parent structure is an array'
|
||||
);
|
||||
} else {
|
||||
throw new \LogicException(
|
||||
'leaveNode() returned invalid value of type ' . gettype($return)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -175,78 +181,81 @@ class NodeTraverser implements NodeTraverserInterface {
|
||||
protected function traverseArray(array $nodes): array {
|
||||
$doNodes = [];
|
||||
|
||||
foreach ($nodes as $i => &$node) {
|
||||
if ($node instanceof Node) {
|
||||
$traverseChildren = true;
|
||||
$visitorIndex = -1;
|
||||
|
||||
foreach ($this->visitors as $visitorIndex => $visitor) {
|
||||
$return = $visitor->enterNode($node);
|
||||
if (null !== $return) {
|
||||
if ($return instanceof Node) {
|
||||
$this->ensureReplacementReasonable($node, $return);
|
||||
$node = $return;
|
||||
} elseif (\is_array($return)) {
|
||||
$doNodes[] = [$i, $return];
|
||||
continue 2;
|
||||
} elseif (NodeVisitor::REMOVE_NODE === $return) {
|
||||
$doNodes[] = [$i, []];
|
||||
continue 2;
|
||||
} elseif (NodeVisitor::DONT_TRAVERSE_CHILDREN === $return) {
|
||||
$traverseChildren = false;
|
||||
} elseif (NodeVisitor::DONT_TRAVERSE_CURRENT_AND_CHILDREN === $return) {
|
||||
$traverseChildren = false;
|
||||
break;
|
||||
} elseif (NodeVisitor::STOP_TRAVERSAL === $return) {
|
||||
$this->stopTraversal = true;
|
||||
break 2;
|
||||
} elseif (NodeVisitor::REPLACE_WITH_NULL === $return) {
|
||||
throw new \LogicException(
|
||||
'REPLACE_WITH_NULL can not be used if the parent structure is an array');
|
||||
} else {
|
||||
throw new \LogicException(
|
||||
'enterNode() returned invalid value of type ' . gettype($return)
|
||||
);
|
||||
}
|
||||
}
|
||||
foreach ($nodes as $i => $node) {
|
||||
if (!$node instanceof Node) {
|
||||
if (\is_array($node)) {
|
||||
throw new \LogicException('Invalid node structure: Contains nested arrays');
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($traverseChildren) {
|
||||
$this->traverseNode($node);
|
||||
if ($this->stopTraversal) {
|
||||
$traverseChildren = true;
|
||||
$visitorIndex = -1;
|
||||
|
||||
foreach ($this->visitors as $visitorIndex => $visitor) {
|
||||
$return = $visitor->enterNode($node);
|
||||
if (null !== $return) {
|
||||
if ($return instanceof Node) {
|
||||
$this->ensureReplacementReasonable($node, $return);
|
||||
$nodes[$i] = $node = $return;
|
||||
} elseif (\is_array($return)) {
|
||||
$doNodes[] = [$i, $return];
|
||||
continue 2;
|
||||
} elseif (NodeVisitor::REMOVE_NODE === $return) {
|
||||
$doNodes[] = [$i, []];
|
||||
continue 2;
|
||||
} elseif (NodeVisitor::DONT_TRAVERSE_CHILDREN === $return) {
|
||||
$traverseChildren = false;
|
||||
} elseif (NodeVisitor::DONT_TRAVERSE_CURRENT_AND_CHILDREN === $return) {
|
||||
$traverseChildren = false;
|
||||
break;
|
||||
} elseif (NodeVisitor::STOP_TRAVERSAL === $return) {
|
||||
$this->stopTraversal = true;
|
||||
break 2;
|
||||
} elseif (NodeVisitor::REPLACE_WITH_NULL === $return) {
|
||||
throw new \LogicException(
|
||||
'REPLACE_WITH_NULL can not be used if the parent structure is an array');
|
||||
} else {
|
||||
throw new \LogicException(
|
||||
'enterNode() returned invalid value of type ' . gettype($return)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (; $visitorIndex >= 0; --$visitorIndex) {
|
||||
$visitor = $this->visitors[$visitorIndex];
|
||||
$return = $visitor->leaveNode($node);
|
||||
if ($traverseChildren) {
|
||||
$this->traverseNode($node);
|
||||
if ($this->stopTraversal) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (null !== $return) {
|
||||
if ($return instanceof Node) {
|
||||
$this->ensureReplacementReasonable($node, $return);
|
||||
$node = $return;
|
||||
} elseif (\is_array($return)) {
|
||||
$doNodes[] = [$i, $return];
|
||||
break;
|
||||
} elseif (NodeVisitor::REMOVE_NODE === $return) {
|
||||
$doNodes[] = [$i, []];
|
||||
break;
|
||||
} elseif (NodeVisitor::STOP_TRAVERSAL === $return) {
|
||||
$this->stopTraversal = true;
|
||||
break 2;
|
||||
} elseif (NodeVisitor::REPLACE_WITH_NULL === $return) {
|
||||
throw new \LogicException(
|
||||
'REPLACE_WITH_NULL can not be used if the parent structure is an array');
|
||||
} else {
|
||||
throw new \LogicException(
|
||||
'leaveNode() returned invalid value of type ' . gettype($return)
|
||||
);
|
||||
}
|
||||
for (; $visitorIndex >= 0; --$visitorIndex) {
|
||||
$visitor = $this->visitors[$visitorIndex];
|
||||
$return = $visitor->leaveNode($node);
|
||||
|
||||
if (null !== $return) {
|
||||
if ($return instanceof Node) {
|
||||
$this->ensureReplacementReasonable($node, $return);
|
||||
$nodes[$i] = $node = $return;
|
||||
} elseif (\is_array($return)) {
|
||||
$doNodes[] = [$i, $return];
|
||||
break;
|
||||
} elseif (NodeVisitor::REMOVE_NODE === $return) {
|
||||
$doNodes[] = [$i, []];
|
||||
break;
|
||||
} elseif (NodeVisitor::STOP_TRAVERSAL === $return) {
|
||||
$this->stopTraversal = true;
|
||||
break 2;
|
||||
} elseif (NodeVisitor::REPLACE_WITH_NULL === $return) {
|
||||
throw new \LogicException(
|
||||
'REPLACE_WITH_NULL can not be used if the parent structure is an array');
|
||||
} else {
|
||||
throw new \LogicException(
|
||||
'leaveNode() returned invalid value of type ' . gettype($return)
|
||||
);
|
||||
}
|
||||
}
|
||||
} elseif (\is_array($node)) {
|
||||
throw new \LogicException('Invalid node structure: Contains nested arrays');
|
||||
}
|
||||
}
|
||||
|
||||
|
82
lib/PhpParser/NodeVisitor/CommentAnnotatingVisitor.php
Normal file
82
lib/PhpParser/NodeVisitor/CommentAnnotatingVisitor.php
Normal file
@@ -0,0 +1,82 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser\NodeVisitor;
|
||||
|
||||
use PhpParser\Comment;
|
||||
use PhpParser\Node;
|
||||
use PhpParser\NodeVisitorAbstract;
|
||||
use PhpParser\Token;
|
||||
|
||||
class CommentAnnotatingVisitor extends NodeVisitorAbstract {
|
||||
/** @var int Last seen token start position */
|
||||
private int $pos = 0;
|
||||
/** @var Token[] Token array */
|
||||
private array $tokens;
|
||||
/** @var list<int> Token positions of comments */
|
||||
private array $commentPositions = [];
|
||||
|
||||
/**
|
||||
* Create a comment annotation visitor.
|
||||
*
|
||||
* @param Token[] $tokens Token array
|
||||
*/
|
||||
public function __construct(array $tokens) {
|
||||
$this->tokens = $tokens;
|
||||
|
||||
// Collect positions of comments. We use this to avoid traversing parts of the AST where
|
||||
// there are no comments.
|
||||
foreach ($tokens as $i => $token) {
|
||||
if ($token->id === \T_COMMENT || $token->id === \T_DOC_COMMENT) {
|
||||
$this->commentPositions[] = $i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function enterNode(Node $node) {
|
||||
$nextCommentPos = current($this->commentPositions);
|
||||
if ($nextCommentPos === false) {
|
||||
// No more comments.
|
||||
return self::STOP_TRAVERSAL;
|
||||
}
|
||||
|
||||
$oldPos = $this->pos;
|
||||
$this->pos = $pos = $node->getStartTokenPos();
|
||||
if ($nextCommentPos > $oldPos && $nextCommentPos < $pos) {
|
||||
$comments = [];
|
||||
while (--$pos >= $oldPos) {
|
||||
$token = $this->tokens[$pos];
|
||||
if ($token->id === \T_DOC_COMMENT) {
|
||||
$comments[] = new Comment\Doc(
|
||||
$token->text, $token->line, $token->pos, $pos,
|
||||
$token->getEndLine(), $token->getEndPos() - 1, $pos);
|
||||
continue;
|
||||
}
|
||||
if ($token->id === \T_COMMENT) {
|
||||
$comments[] = new Comment(
|
||||
$token->text, $token->line, $token->pos, $pos,
|
||||
$token->getEndLine(), $token->getEndPos() - 1, $pos);
|
||||
continue;
|
||||
}
|
||||
if ($token->id !== \T_WHITESPACE) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!empty($comments)) {
|
||||
$node->setAttribute('comments', array_reverse($comments));
|
||||
}
|
||||
|
||||
do {
|
||||
$nextCommentPos = next($this->commentPositions);
|
||||
} while ($nextCommentPos !== false && $nextCommentPos < $this->pos);
|
||||
}
|
||||
|
||||
$endPos = $node->getEndTokenPos();
|
||||
if ($nextCommentPos > $endPos) {
|
||||
// Skip children if there are no comments located inside this node.
|
||||
$this->pos = $endPos;
|
||||
return self::DONT_TRAVERSE_CHILDREN;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
@@ -12,7 +12,7 @@ use PhpParser\NodeVisitorAbstract;
|
||||
class FindingVisitor extends NodeVisitorAbstract {
|
||||
/** @var callable Filter callback */
|
||||
protected $filterCallback;
|
||||
/** @var Node[] Found nodes */
|
||||
/** @var list<Node> Found nodes */
|
||||
protected array $foundNodes;
|
||||
|
||||
public function __construct(callable $filterCallback) {
|
||||
@@ -24,7 +24,7 @@ class FindingVisitor extends NodeVisitorAbstract {
|
||||
*
|
||||
* Nodes are returned in pre-order.
|
||||
*
|
||||
* @return Node[] Found nodes
|
||||
* @return list<Node> Found nodes
|
||||
*/
|
||||
public function getFoundNodes(): array {
|
||||
return $this->foundNodes;
|
||||
|
@@ -110,11 +110,21 @@ class NameResolver extends NodeVisitorAbstract {
|
||||
$node->type = $this->resolveType($node->type);
|
||||
}
|
||||
$this->resolveAttrGroups($node);
|
||||
} elseif ($node instanceof Node\PropertyHook) {
|
||||
foreach ($node->params as $param) {
|
||||
$param->type = $this->resolveType($param->type);
|
||||
$this->resolveAttrGroups($param);
|
||||
}
|
||||
$this->resolveAttrGroups($node);
|
||||
} elseif ($node instanceof Stmt\Const_) {
|
||||
foreach ($node->consts as $const) {
|
||||
$this->addNamespacedName($const);
|
||||
}
|
||||
$this->resolveAttrGroups($node);
|
||||
} elseif ($node instanceof Stmt\ClassConst) {
|
||||
if (null !== $node->type) {
|
||||
$node->type = $this->resolveType($node->type);
|
||||
}
|
||||
$this->resolveAttrGroups($node);
|
||||
} elseif ($node instanceof Stmt\EnumCase) {
|
||||
$this->resolveAttrGroups($node);
|
||||
|
@@ -9,10 +9,12 @@ use PhpParser\NodeVisitorAbstract;
|
||||
* Visitor that connects a child node to its parent node
|
||||
* as well as its sibling nodes.
|
||||
*
|
||||
* On the child node, the parent node can be accessed through
|
||||
* With <code>$weakReferences=false</code> on the child node, the parent node can be accessed through
|
||||
* <code>$node->getAttribute('parent')</code>, the previous
|
||||
* node can be accessed through <code>$node->getAttribute('previous')</code>,
|
||||
* and the next node can be accessed through <code>$node->getAttribute('next')</code>.
|
||||
*
|
||||
* With <code>$weakReferences=true</code> attribute names are prefixed by "weak_", e.g. "weak_parent".
|
||||
*/
|
||||
final class NodeConnectingVisitor extends NodeVisitorAbstract {
|
||||
/**
|
||||
@@ -25,6 +27,12 @@ final class NodeConnectingVisitor extends NodeVisitorAbstract {
|
||||
*/
|
||||
private $previous;
|
||||
|
||||
private bool $weakReferences;
|
||||
|
||||
public function __construct(bool $weakReferences = false) {
|
||||
$this->weakReferences = $weakReferences;
|
||||
}
|
||||
|
||||
public function beforeTraverse(array $nodes) {
|
||||
$this->stack = [];
|
||||
$this->previous = null;
|
||||
@@ -32,12 +40,26 @@ final class NodeConnectingVisitor extends NodeVisitorAbstract {
|
||||
|
||||
public function enterNode(Node $node) {
|
||||
if (!empty($this->stack)) {
|
||||
$node->setAttribute('parent', $this->stack[count($this->stack) - 1]);
|
||||
$parent = $this->stack[count($this->stack) - 1];
|
||||
if ($this->weakReferences) {
|
||||
$node->setAttribute('weak_parent', \WeakReference::create($parent));
|
||||
} else {
|
||||
$node->setAttribute('parent', $parent);
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->previous !== null && $this->previous->getAttribute('parent') === $node->getAttribute('parent')) {
|
||||
$node->setAttribute('previous', $this->previous);
|
||||
$this->previous->setAttribute('next', $node);
|
||||
if ($this->previous !== null) {
|
||||
if (
|
||||
$this->weakReferences
|
||||
) {
|
||||
if ($this->previous->getAttribute('weak_parent') === $node->getAttribute('weak_parent')) {
|
||||
$node->setAttribute('weak_previous', \WeakReference::create($this->previous));
|
||||
$this->previous->setAttribute('weak_next', \WeakReference::create($node));
|
||||
}
|
||||
} elseif ($this->previous->getAttribute('parent') === $node->getAttribute('parent')) {
|
||||
$node->setAttribute('previous', $this->previous);
|
||||
$this->previous->setAttribute('next', $node);
|
||||
}
|
||||
}
|
||||
|
||||
$this->stack[] = $node;
|
||||
|
@@ -11,8 +11,10 @@ use function count;
|
||||
/**
|
||||
* Visitor that connects a child node to its parent node.
|
||||
*
|
||||
* On the child node, the parent node can be accessed through
|
||||
* With <code>$weakReferences=false</code> on the child node, the parent node can be accessed through
|
||||
* <code>$node->getAttribute('parent')</code>.
|
||||
*
|
||||
* With <code>$weakReferences=true</code> the attribute name is "weak_parent" instead.
|
||||
*/
|
||||
final class ParentConnectingVisitor extends NodeVisitorAbstract {
|
||||
/**
|
||||
@@ -20,13 +22,24 @@ final class ParentConnectingVisitor extends NodeVisitorAbstract {
|
||||
*/
|
||||
private array $stack = [];
|
||||
|
||||
private bool $weakReferences;
|
||||
|
||||
public function __construct(bool $weakReferences = false) {
|
||||
$this->weakReferences = $weakReferences;
|
||||
}
|
||||
|
||||
public function beforeTraverse(array $nodes) {
|
||||
$this->stack = [];
|
||||
}
|
||||
|
||||
public function enterNode(Node $node) {
|
||||
if (!empty($this->stack)) {
|
||||
$node->setAttribute('parent', $this->stack[count($this->stack) - 1]);
|
||||
$parent = $this->stack[count($this->stack) - 1];
|
||||
if ($this->weakReferences) {
|
||||
$node->setAttribute('weak_parent', \WeakReference::create($parent));
|
||||
} else {
|
||||
$node->setAttribute('parent', $parent);
|
||||
}
|
||||
}
|
||||
|
||||
$this->stack[] = $node;
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -7,6 +7,7 @@ namespace PhpParser;
|
||||
* turn is based on work by Masato Bito.
|
||||
*/
|
||||
|
||||
use PhpParser\Node\Arg;
|
||||
use PhpParser\Node\Expr;
|
||||
use PhpParser\Node\Expr\Array_;
|
||||
use PhpParser\Node\Expr\Cast\Double;
|
||||
@@ -14,6 +15,7 @@ use PhpParser\Node\Identifier;
|
||||
use PhpParser\Node\InterpolatedStringPart;
|
||||
use PhpParser\Node\Name;
|
||||
use PhpParser\Node\Param;
|
||||
use PhpParser\Node\PropertyHook;
|
||||
use PhpParser\Node\Scalar\InterpolatedString;
|
||||
use PhpParser\Node\Scalar\Int_;
|
||||
use PhpParser\Node\Scalar\String_;
|
||||
@@ -21,6 +23,7 @@ use PhpParser\Node\Stmt;
|
||||
use PhpParser\Node\Stmt\Class_;
|
||||
use PhpParser\Node\Stmt\ClassConst;
|
||||
use PhpParser\Node\Stmt\ClassMethod;
|
||||
use PhpParser\Node\Stmt\Const_;
|
||||
use PhpParser\Node\Stmt\Else_;
|
||||
use PhpParser\Node\Stmt\ElseIf_;
|
||||
use PhpParser\Node\Stmt\Enum_;
|
||||
@@ -30,6 +33,8 @@ use PhpParser\Node\Stmt\Nop;
|
||||
use PhpParser\Node\Stmt\Property;
|
||||
use PhpParser\Node\Stmt\TryCatch;
|
||||
use PhpParser\Node\UseItem;
|
||||
use PhpParser\Node\VarLikeIdentifier;
|
||||
use PhpParser\NodeVisitor\CommentAnnotatingVisitor;
|
||||
|
||||
abstract class ParserAbstract implements Parser {
|
||||
private const SYMBOL_NONE = -1;
|
||||
@@ -201,6 +206,11 @@ abstract class ParserAbstract implements Parser {
|
||||
$this->semValue = null;
|
||||
$this->createdArrays = null;
|
||||
|
||||
if ($result !== null) {
|
||||
$traverser = new NodeTraverser(new CommentAnnotatingVisitor($this->tokens));
|
||||
$traverser->traverse($result);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
@@ -312,7 +322,7 @@ abstract class ParserAbstract implements Parser {
|
||||
try {
|
||||
$callback = $this->reduceCallbacks[$rule];
|
||||
if ($callback !== null) {
|
||||
$callback($stackPos);
|
||||
$callback($this, $stackPos);
|
||||
} elseif ($ruleLength > 0) {
|
||||
$this->semValue = $this->semStack[$stackPos - $ruleLength + 1];
|
||||
}
|
||||
@@ -403,8 +413,6 @@ abstract class ParserAbstract implements Parser {
|
||||
$rule = $state - $this->numNonLeafStates;
|
||||
}
|
||||
}
|
||||
|
||||
throw new \RuntimeException('Reached end of parser loop');
|
||||
}
|
||||
|
||||
protected function emitError(Error $error): void {
|
||||
@@ -473,7 +481,7 @@ abstract class ParserAbstract implements Parser {
|
||||
protected function getAttributes(int $tokenStartPos, int $tokenEndPos): array {
|
||||
$startToken = $this->tokens[$tokenStartPos];
|
||||
$afterEndToken = $this->tokens[$tokenEndPos + 1];
|
||||
$attributes = [
|
||||
return [
|
||||
'startLine' => $startToken->line,
|
||||
'startTokenPos' => $tokenStartPos,
|
||||
'startFilePos' => $startToken->pos,
|
||||
@@ -481,11 +489,6 @@ abstract class ParserAbstract implements Parser {
|
||||
'endTokenPos' => $tokenEndPos,
|
||||
'endFilePos' => $afterEndToken->pos - 1,
|
||||
];
|
||||
$comments = $this->getCommentsBeforeToken($tokenStartPos);
|
||||
if (!empty($comments)) {
|
||||
$attributes['comments'] = $comments;
|
||||
}
|
||||
return $attributes;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -500,7 +503,7 @@ abstract class ParserAbstract implements Parser {
|
||||
|
||||
// Get attributes for the sentinel token.
|
||||
$token = $this->tokens[$tokenPos];
|
||||
$attributes = [
|
||||
return [
|
||||
'startLine' => $token->line,
|
||||
'startTokenPos' => $tokenPos,
|
||||
'startFilePos' => $token->pos,
|
||||
@@ -508,11 +511,6 @@ abstract class ParserAbstract implements Parser {
|
||||
'endTokenPos' => $tokenPos,
|
||||
'endFilePos' => $token->pos,
|
||||
];
|
||||
$comments = $this->getCommentsBeforeToken($tokenPos);
|
||||
if (!empty($comments)) {
|
||||
$attributes['comments'] = $comments;
|
||||
}
|
||||
return $attributes;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -585,7 +583,7 @@ abstract class ParserAbstract implements Parser {
|
||||
} else {
|
||||
// For semicolon namespaces we have to move the statements after a namespace declaration into ->stmts
|
||||
$resultStmts = [];
|
||||
$targetStmts =& $resultStmts;
|
||||
$targetStmts = &$resultStmts;
|
||||
$lastNs = null;
|
||||
foreach ($stmts as $stmt) {
|
||||
if ($stmt instanceof Node\Stmt\Namespace_) {
|
||||
@@ -594,12 +592,12 @@ abstract class ParserAbstract implements Parser {
|
||||
}
|
||||
if ($stmt->stmts === null) {
|
||||
$stmt->stmts = [];
|
||||
$targetStmts =& $stmt->stmts;
|
||||
$targetStmts = &$stmt->stmts;
|
||||
$resultStmts[] = $stmt;
|
||||
} else {
|
||||
// This handles the invalid case of mixed style namespaces
|
||||
$resultStmts[] = $stmt;
|
||||
$targetStmts =& $resultStmts;
|
||||
$targetStmts = &$resultStmts;
|
||||
}
|
||||
$lastNs = $stmt;
|
||||
} elseif ($stmt instanceof Node\Stmt\HaltCompiler) {
|
||||
@@ -845,6 +843,7 @@ abstract class ParserAbstract implements Parser {
|
||||
|
||||
if (\is_string($contents)) {
|
||||
if ($contents === '') {
|
||||
$attributes['rawValue'] = $contents;
|
||||
return new String_('', $attributes);
|
||||
}
|
||||
|
||||
@@ -852,6 +851,7 @@ abstract class ParserAbstract implements Parser {
|
||||
$contents, $indentLen, $indentChar, true, true, $attributes
|
||||
);
|
||||
$contents = preg_replace('~(\r\n|\n|\r)\z~', '', $contents);
|
||||
$attributes['rawValue'] = $contents;
|
||||
|
||||
if ($kind === String_::KIND_HEREDOC) {
|
||||
$contents = String_::parseEscapeSequences($contents, null, $parseUnicodeEscape);
|
||||
@@ -878,6 +878,7 @@ abstract class ParserAbstract implements Parser {
|
||||
if ($isLast) {
|
||||
$part->value = preg_replace('~(\r\n|\n|\r)\z~', '', $part->value);
|
||||
}
|
||||
$part->setAttribute('rawValue', $part->value);
|
||||
$part->value = String_::parseEscapeSequences($part->value, null, $parseUnicodeEscape);
|
||||
if ('' === $part->value) {
|
||||
continue;
|
||||
@@ -899,12 +900,9 @@ abstract class ParserAbstract implements Parser {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get comments before the given token position.
|
||||
*
|
||||
* @return Comment[] Comments
|
||||
* Get last comment before the given token position, if any
|
||||
*/
|
||||
protected function getCommentsBeforeToken(int $tokenPos): array {
|
||||
$comments = [];
|
||||
protected function getCommentBeforeToken(int $tokenPos): ?Comment {
|
||||
while (--$tokenPos >= 0) {
|
||||
$token = $this->tokens[$tokenPos];
|
||||
if (!isset($this->dropTokens[$token->id])) {
|
||||
@@ -912,22 +910,21 @@ abstract class ParserAbstract implements Parser {
|
||||
}
|
||||
|
||||
if ($token->id === \T_COMMENT || $token->id === \T_DOC_COMMENT) {
|
||||
$comments[] = $this->createCommentFromToken($token, $tokenPos);
|
||||
return $this->createCommentFromToken($token, $tokenPos);
|
||||
}
|
||||
}
|
||||
return \array_reverse($comments);
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a zero-length nop to capture preceding comments, if any.
|
||||
*/
|
||||
protected function maybeCreateZeroLengthNop(int $tokenPos): ?Nop {
|
||||
$comments = $this->getCommentsBeforeToken($tokenPos);
|
||||
if (empty($comments)) {
|
||||
$comment = $this->getCommentBeforeToken($tokenPos);
|
||||
if ($comment === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$comment = $comments[\count($comments) - 1];
|
||||
$commentEndLine = $comment->getEndLine();
|
||||
$commentEndFilePos = $comment->getEndFilePos();
|
||||
$commentEndTokenPos = $comment->getEndTokenPos();
|
||||
@@ -938,14 +935,12 @@ abstract class ParserAbstract implements Parser {
|
||||
'endFilePos' => $commentEndFilePos,
|
||||
'startTokenPos' => $commentEndTokenPos + 1,
|
||||
'endTokenPos' => $commentEndTokenPos,
|
||||
'comments' => $comments,
|
||||
];
|
||||
return new Nop($attributes);
|
||||
}
|
||||
|
||||
protected function maybeCreateNop(int $tokenStartPos, int $tokenEndPos): ?Nop {
|
||||
$comments = $this->getCommentsBeforeToken($tokenStartPos);
|
||||
if (empty($comments)) {
|
||||
if ($this->getCommentBeforeToken($tokenStartPos) === null) {
|
||||
return null;
|
||||
}
|
||||
return new Nop($this->getAttributes($tokenStartPos, $tokenEndPos));
|
||||
@@ -1144,32 +1139,12 @@ abstract class ParserAbstract implements Parser {
|
||||
}
|
||||
|
||||
protected function checkClassConst(ClassConst $node, int $modifierPos): void {
|
||||
if ($node->flags & Modifiers::STATIC) {
|
||||
$this->emitError(new Error(
|
||||
"Cannot use 'static' as constant modifier",
|
||||
$this->getAttributesAt($modifierPos)));
|
||||
}
|
||||
if ($node->flags & Modifiers::ABSTRACT) {
|
||||
$this->emitError(new Error(
|
||||
"Cannot use 'abstract' as constant modifier",
|
||||
$this->getAttributesAt($modifierPos)));
|
||||
}
|
||||
if ($node->flags & Modifiers::READONLY) {
|
||||
$this->emitError(new Error(
|
||||
"Cannot use 'readonly' as constant modifier",
|
||||
$this->getAttributesAt($modifierPos)));
|
||||
}
|
||||
}
|
||||
|
||||
protected function checkProperty(Property $node, int $modifierPos): void {
|
||||
if ($node->flags & Modifiers::ABSTRACT) {
|
||||
$this->emitError(new Error('Properties cannot be declared abstract',
|
||||
$this->getAttributesAt($modifierPos)));
|
||||
}
|
||||
|
||||
if ($node->flags & Modifiers::FINAL) {
|
||||
$this->emitError(new Error('Properties cannot be declared final',
|
||||
$this->getAttributesAt($modifierPos)));
|
||||
foreach ([Modifiers::STATIC, Modifiers::ABSTRACT, Modifiers::READONLY] as $modifier) {
|
||||
if ($node->flags & $modifier) {
|
||||
$this->emitError(new Error(
|
||||
"Cannot use '" . Modifiers::toString($modifier) . "' as constant modifier",
|
||||
$this->getAttributesAt($modifierPos)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1185,6 +1160,96 @@ abstract class ParserAbstract implements Parser {
|
||||
}
|
||||
}
|
||||
|
||||
protected function checkPropertyHooksForMultiProperty(Property $property, int $hookPos): void {
|
||||
if (count($property->props) > 1) {
|
||||
$this->emitError(new Error(
|
||||
'Cannot use hooks when declaring multiple properties', $this->getAttributesAt($hookPos)));
|
||||
}
|
||||
}
|
||||
|
||||
/** @param PropertyHook[] $hooks */
|
||||
protected function checkEmptyPropertyHookList(array $hooks, int $hookPos): void {
|
||||
if (empty($hooks)) {
|
||||
$this->emitError(new Error(
|
||||
'Property hook list cannot be empty', $this->getAttributesAt($hookPos)));
|
||||
}
|
||||
}
|
||||
|
||||
protected function checkPropertyHook(PropertyHook $hook, ?int $paramListPos): void {
|
||||
$name = $hook->name->toLowerString();
|
||||
if ($name !== 'get' && $name !== 'set') {
|
||||
$this->emitError(new Error(
|
||||
'Unknown hook "' . $hook->name . '", expected "get" or "set"',
|
||||
$hook->name->getAttributes()));
|
||||
}
|
||||
if ($name === 'get' && $paramListPos !== null) {
|
||||
$this->emitError(new Error(
|
||||
'get hook must not have a parameter list', $this->getAttributesAt($paramListPos)));
|
||||
}
|
||||
}
|
||||
|
||||
protected function checkPropertyHookModifiers(int $a, int $b, int $modifierPos): void {
|
||||
try {
|
||||
Modifiers::verifyModifier($a, $b);
|
||||
} catch (Error $error) {
|
||||
$error->setAttributes($this->getAttributesAt($modifierPos));
|
||||
$this->emitError($error);
|
||||
}
|
||||
|
||||
if ($b != Modifiers::FINAL) {
|
||||
$this->emitError(new Error(
|
||||
'Cannot use the ' . Modifiers::toString($b) . ' modifier on a property hook',
|
||||
$this->getAttributesAt($modifierPos)));
|
||||
}
|
||||
}
|
||||
|
||||
protected function checkConstantAttributes(Const_ $node): void {
|
||||
if ($node->attrGroups !== [] && count($node->consts) > 1) {
|
||||
$this->emitError(new Error(
|
||||
'Cannot use attributes on multiple constants at once', $node->getAttributes()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Property|Param $node
|
||||
*/
|
||||
protected function addPropertyNameToHooks(Node $node): void {
|
||||
if ($node instanceof Property) {
|
||||
$name = $node->props[0]->name->toString();
|
||||
} else {
|
||||
$name = $node->var->name;
|
||||
}
|
||||
foreach ($node->hooks as $hook) {
|
||||
$hook->setAttribute('propertyName', $name);
|
||||
}
|
||||
}
|
||||
|
||||
/** @param array<Node\Arg|Node\VariadicPlaceholder> $args */
|
||||
private function isSimpleExit(array $args): bool {
|
||||
if (\count($args) === 0) {
|
||||
return true;
|
||||
}
|
||||
if (\count($args) === 1) {
|
||||
$arg = $args[0];
|
||||
return $arg instanceof Arg && $arg->name === null &&
|
||||
$arg->byRef === false && $arg->unpack === false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<Node\Arg|Node\VariadicPlaceholder> $args
|
||||
* @param array<string, mixed> $attrs
|
||||
*/
|
||||
protected function createExitExpr(string $name, int $namePos, array $args, array $attrs): Expr {
|
||||
if ($this->isSimpleExit($args)) {
|
||||
// Create Exit node for backwards compatibility.
|
||||
$attrs['kind'] = strtolower($name) === 'exit' ? Expr\Exit_::KIND_EXIT : Expr\Exit_::KIND_DIE;
|
||||
return new Expr\Exit_(\count($args) === 1 ? $args[0]->value : null, $attrs);
|
||||
}
|
||||
return new Expr\FuncCall(new Name($name, $this->getAttributesAt($namePos)), $args, $attrs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the token map.
|
||||
*
|
||||
@@ -1197,42 +1262,23 @@ abstract class ParserAbstract implements Parser {
|
||||
protected function createTokenMap(): array {
|
||||
$tokenMap = [];
|
||||
|
||||
for ($i = 0; $i < 1000; ++$i) {
|
||||
if ($i < 256) {
|
||||
// Single-char tokens use an identity mapping.
|
||||
$tokenMap[$i] = $i;
|
||||
} elseif (\T_DOUBLE_COLON === $i) {
|
||||
// T_DOUBLE_COLON is equivalent to T_PAAMAYIM_NEKUDOTAYIM
|
||||
$tokenMap[$i] = static::T_PAAMAYIM_NEKUDOTAYIM;
|
||||
} elseif (\T_OPEN_TAG_WITH_ECHO === $i) {
|
||||
// T_OPEN_TAG_WITH_ECHO with dropped T_OPEN_TAG results in T_ECHO
|
||||
$tokenMap[$i] = static::T_ECHO;
|
||||
} elseif (\T_CLOSE_TAG === $i) {
|
||||
// T_CLOSE_TAG is equivalent to ';'
|
||||
$tokenMap[$i] = ord(';');
|
||||
} elseif ('UNKNOWN' !== $name = token_name($i)) {
|
||||
if (defined($name = static::class . '::' . $name)) {
|
||||
// Other tokens can be mapped directly
|
||||
$tokenMap[$i] = constant($name);
|
||||
}
|
||||
// Single-char tokens use an identity mapping.
|
||||
for ($i = 0; $i < 256; ++$i) {
|
||||
$tokenMap[$i] = $i;
|
||||
}
|
||||
|
||||
foreach ($this->symbolToName as $name) {
|
||||
if ($name[0] === 'T') {
|
||||
$tokenMap[\constant($name)] = constant(static::class . '::' . $name);
|
||||
}
|
||||
}
|
||||
|
||||
// Assign tokens for which we define compatibility constants, as token_name() does not know them.
|
||||
$tokenMap[\T_FN] = static::T_FN;
|
||||
$tokenMap[\T_COALESCE_EQUAL] = static::T_COALESCE_EQUAL;
|
||||
$tokenMap[\T_NAME_QUALIFIED] = static::T_NAME_QUALIFIED;
|
||||
$tokenMap[\T_NAME_FULLY_QUALIFIED] = static::T_NAME_FULLY_QUALIFIED;
|
||||
$tokenMap[\T_NAME_RELATIVE] = static::T_NAME_RELATIVE;
|
||||
$tokenMap[\T_MATCH] = static::T_MATCH;
|
||||
$tokenMap[\T_NULLSAFE_OBJECT_OPERATOR] = static::T_NULLSAFE_OBJECT_OPERATOR;
|
||||
$tokenMap[\T_ATTRIBUTE] = static::T_ATTRIBUTE;
|
||||
$tokenMap[\T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG] = static::T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG;
|
||||
$tokenMap[\T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG] = static::T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG;
|
||||
$tokenMap[\T_ENUM] = static::T_ENUM;
|
||||
$tokenMap[\T_READONLY] = static::T_READONLY;
|
||||
// T_OPEN_TAG_WITH_ECHO with dropped T_OPEN_TAG results in T_ECHO
|
||||
$tokenMap[\T_OPEN_TAG_WITH_ECHO] = static::T_ECHO;
|
||||
// T_CLOSE_TAG is equivalent to ';'
|
||||
$tokenMap[\T_CLOSE_TAG] = ord(';');
|
||||
|
||||
// We have create a map from PHP token IDs to external symbol IDs.
|
||||
// We have created a map from PHP token IDs to external symbol IDs.
|
||||
// Now map them to the internal symbol ID.
|
||||
$fullTokenMap = [];
|
||||
foreach ($tokenMap as $phpToken => $extSymbol) {
|
||||
|
@@ -6,34 +6,6 @@ use PhpParser\Parser\Php7;
|
||||
use PhpParser\Parser\Php8;
|
||||
|
||||
class ParserFactory {
|
||||
public const PREFER_PHP7 = 1;
|
||||
public const ONLY_PHP7 = 3;
|
||||
|
||||
/**
|
||||
* Creates a Parser instance, according to the provided kind.
|
||||
*
|
||||
* @param int $kind One of ::PREFER_PHP7 or ::ONLY_PHP7
|
||||
* @param Lexer|null $lexer Lexer to use. Defaults to emulative lexer when not specified
|
||||
*
|
||||
* @return Parser The parser instance
|
||||
*
|
||||
* @deprecated Use createForVersion(), createForNewestSupportedVersion() or createForHostVersion() instead.
|
||||
*/
|
||||
public function create(int $kind, ?Lexer $lexer = null): Parser {
|
||||
if (null === $lexer) {
|
||||
$lexer = new Lexer\Emulative();
|
||||
}
|
||||
switch ($kind) {
|
||||
case self::PREFER_PHP7:
|
||||
case self::ONLY_PHP7:
|
||||
return new Parser\Php7($lexer);
|
||||
default:
|
||||
throw new \LogicException(
|
||||
'Kind must be one of ::PREFER_PHP7 or ::ONLY_PHP7'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a parser targeting the given version on a best-effort basis. The parser will generally
|
||||
* accept code for the newest supported version, but will try to accommodate code that becomes
|
||||
|
@@ -43,7 +43,7 @@ class PhpVersion {
|
||||
* if it is still under development.
|
||||
*/
|
||||
public static function getNewestSupported(): self {
|
||||
return self::fromComponents(8, 2);
|
||||
return self::fromComponents(8, 5);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -149,9 +149,23 @@ class PhpVersion {
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this version support yield in expression context without parentheses.
|
||||
* Whether this version supports yield in expression context without parentheses.
|
||||
*/
|
||||
public function supportsYieldWithoutParentheses(): bool {
|
||||
return $this->id >= 70000;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this version supports unicode escape sequences in strings.
|
||||
*/
|
||||
public function supportsUnicodeEscapes(): bool {
|
||||
return $this->id >= 70000;
|
||||
}
|
||||
|
||||
/*
|
||||
* Whether this version supports attributes.
|
||||
*/
|
||||
public function supportsAttributes(): bool {
|
||||
return $this->id >= 80000;
|
||||
}
|
||||
}
|
||||
|
@@ -17,13 +17,14 @@ class Standard extends PrettyPrinterAbstract {
|
||||
// Special nodes
|
||||
|
||||
protected function pParam(Node\Param $node): string {
|
||||
return $this->pAttrGroups($node->attrGroups, true)
|
||||
return $this->pAttrGroups($node->attrGroups, $this->phpVersion->supportsAttributes())
|
||||
. $this->pModifiers($node->flags)
|
||||
. ($node->type ? $this->p($node->type) . ' ' : '')
|
||||
. ($node->byRef ? '&' : '')
|
||||
. ($node->variadic ? '...' : '')
|
||||
. $this->p($node->var)
|
||||
. ($node->default ? ' = ' . $this->p($node->default) : '');
|
||||
. ($node->default ? ' = ' . $this->p($node->default) : '')
|
||||
. ($node->hooks ? ' {' . $this->pStmts($node->hooks) . $this->nl . '}' : '');
|
||||
}
|
||||
|
||||
protected function pArg(Node\Arg $node): string {
|
||||
@@ -125,6 +126,10 @@ class Standard extends PrettyPrinterAbstract {
|
||||
return '__TRAIT__';
|
||||
}
|
||||
|
||||
protected function pScalar_MagicConst_Property(MagicConst\Property $node): string {
|
||||
return '__PROPERTY__';
|
||||
}
|
||||
|
||||
// Scalars
|
||||
|
||||
private function indentString(string $str): string {
|
||||
@@ -420,6 +425,10 @@ class Standard extends PrettyPrinterAbstract {
|
||||
return $this->pInfixOp(BinaryOp\Coalesce::class, $node->left, ' ?? ', $node->right, $precedence, $lhsPrecedence);
|
||||
}
|
||||
|
||||
protected function pExpr_BinaryOp_Pipe(BinaryOp\Pipe $node, int $precedence, int $lhsPrecedence): string {
|
||||
return $this->pInfixOp(BinaryOp\Pipe::class, $node->left, ' |> ', $node->right, $precedence, $lhsPrecedence);
|
||||
}
|
||||
|
||||
protected function pExpr_Instanceof(Expr\Instanceof_ $node, int $precedence, int $lhsPrecedence): string {
|
||||
return $this->pPostfixOp(
|
||||
Expr\Instanceof_::class, $node->expr,
|
||||
@@ -512,6 +521,10 @@ class Standard extends PrettyPrinterAbstract {
|
||||
return $this->pPrefixOp(Cast\Unset_::class, '(unset) ', $node->expr, $precedence, $lhsPrecedence);
|
||||
}
|
||||
|
||||
protected function pExpr_Cast_Void(Cast\Void_ $node, int $precedence, int $lhsPrecedence): string {
|
||||
return $this->pPrefixOp(Cast\Void_::class, '(void) ', $node->expr, $precedence, $lhsPrecedence);
|
||||
}
|
||||
|
||||
// Function calls and similar constructs
|
||||
|
||||
protected function pExpr_FuncCall(Expr\FuncCall $node): string {
|
||||
@@ -651,7 +664,7 @@ class Standard extends PrettyPrinterAbstract {
|
||||
return $this->pAttrGroups($node->attrGroups, true)
|
||||
. $this->pStatic($node->static)
|
||||
. 'function ' . ($node->byRef ? '&' : '')
|
||||
. '(' . $this->pMaybeMultiline($node->params, $this->phpVersion->supportsTrailingCommaInParamList()) . ')'
|
||||
. '(' . $this->pParams($node->params) . ')'
|
||||
. (!empty($node->uses) ? ' use (' . $this->pCommaSeparated($node->uses) . ')' : '')
|
||||
. (null !== $node->returnType ? ': ' . $this->p($node->returnType) : '')
|
||||
. ' {' . $this->pStmts($node->stmts) . $this->nl . '}';
|
||||
@@ -683,7 +696,7 @@ class Standard extends PrettyPrinterAbstract {
|
||||
$this->pAttrGroups($node->attrGroups, true)
|
||||
. $this->pStatic($node->static)
|
||||
. 'fn' . ($node->byRef ? '&' : '')
|
||||
. '(' . $this->pMaybeMultiline($node->params, $this->phpVersion->supportsTrailingCommaInParamList()) . ')'
|
||||
. '(' . $this->pParams($node->params) . ')'
|
||||
. (null !== $node->returnType ? ': ' . $this->p($node->returnType) : '')
|
||||
. ' => ',
|
||||
$node->expr, $precedence, $lhsPrecedence);
|
||||
@@ -827,7 +840,8 @@ class Standard extends PrettyPrinterAbstract {
|
||||
return $this->pAttrGroups($node->attrGroups)
|
||||
. (0 === $node->flags ? 'var ' : $this->pModifiers($node->flags))
|
||||
. ($node->type ? $this->p($node->type) . ' ' : '')
|
||||
. $this->pCommaSeparated($node->props) . ';';
|
||||
. $this->pCommaSeparated($node->props)
|
||||
. ($node->hooks ? ' {' . $this->pStmts($node->hooks) . $this->nl . '}' : ';');
|
||||
}
|
||||
|
||||
protected function pPropertyItem(Node\PropertyItem $node): string {
|
||||
@@ -835,11 +849,20 @@ class Standard extends PrettyPrinterAbstract {
|
||||
. (null !== $node->default ? ' = ' . $this->p($node->default) : '');
|
||||
}
|
||||
|
||||
protected function pPropertyHook(Node\PropertyHook $node): string {
|
||||
return $this->pAttrGroups($node->attrGroups)
|
||||
. $this->pModifiers($node->flags)
|
||||
. ($node->byRef ? '&' : '') . $node->name
|
||||
. ($node->params ? '(' . $this->pParams($node->params) . ')' : '')
|
||||
. (\is_array($node->body) ? ' {' . $this->pStmts($node->body) . $this->nl . '}'
|
||||
: ($node->body !== null ? ' => ' . $this->p($node->body) : '') . ';');
|
||||
}
|
||||
|
||||
protected function pStmt_ClassMethod(Stmt\ClassMethod $node): string {
|
||||
return $this->pAttrGroups($node->attrGroups)
|
||||
. $this->pModifiers($node->flags)
|
||||
. 'function ' . ($node->byRef ? '&' : '') . $node->name
|
||||
. '(' . $this->pMaybeMultiline($node->params, $this->phpVersion->supportsTrailingCommaInParamList()) . ')'
|
||||
. '(' . $this->pParams($node->params) . ')'
|
||||
. (null !== $node->returnType ? ': ' . $this->p($node->returnType) : '')
|
||||
. (null !== $node->stmts
|
||||
? $this->nl . '{' . $this->pStmts($node->stmts) . $this->nl . '}'
|
||||
@@ -857,13 +880,15 @@ class Standard extends PrettyPrinterAbstract {
|
||||
protected function pStmt_Function(Stmt\Function_ $node): string {
|
||||
return $this->pAttrGroups($node->attrGroups)
|
||||
. 'function ' . ($node->byRef ? '&' : '') . $node->name
|
||||
. '(' . $this->pMaybeMultiline($node->params, $this->phpVersion->supportsTrailingCommaInParamList()) . ')'
|
||||
. '(' . $this->pParams($node->params) . ')'
|
||||
. (null !== $node->returnType ? ': ' . $this->p($node->returnType) : '')
|
||||
. $this->nl . '{' . $this->pStmts($node->stmts) . $this->nl . '}';
|
||||
}
|
||||
|
||||
protected function pStmt_Const(Stmt\Const_ $node): string {
|
||||
return 'const ' . $this->pCommaSeparated($node->consts) . ';';
|
||||
return $this->pAttrGroups($node->attrGroups)
|
||||
. 'const '
|
||||
. $this->pCommaSeparated($node->consts) . ';';
|
||||
}
|
||||
|
||||
protected function pStmt_Declare(Stmt\Declare_ $node): string {
|
||||
@@ -960,10 +985,6 @@ class Standard extends PrettyPrinterAbstract {
|
||||
return 'return' . (null !== $node->expr ? ' ' . $this->p($node->expr) : '') . ';';
|
||||
}
|
||||
|
||||
protected function pStmt_Throw(Stmt\Throw_ $node): string {
|
||||
return 'throw ' . $this->p($node->expr) . ';';
|
||||
}
|
||||
|
||||
protected function pStmt_Label(Stmt\Label $node): string {
|
||||
return $node->name . ':';
|
||||
}
|
||||
@@ -1012,6 +1033,10 @@ class Standard extends PrettyPrinterAbstract {
|
||||
return '';
|
||||
}
|
||||
|
||||
protected function pStmt_Block(Stmt\Block $node): string {
|
||||
return '{' . $this->pStmts($node->stmts) . $this->nl . '}';
|
||||
}
|
||||
|
||||
// Helpers
|
||||
|
||||
protected function pClassCommon(Stmt\Class_ $node, string $afterClassToken): string {
|
||||
@@ -1164,6 +1189,27 @@ class Standard extends PrettyPrinterAbstract {
|
||||
}
|
||||
}
|
||||
|
||||
/** @param Node\Param[] $params
|
||||
*/
|
||||
private function hasParamWithAttributes(array $params): bool {
|
||||
foreach ($params as $param) {
|
||||
if ($param->attrGroups) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/** @param Node\Param[] $params */
|
||||
protected function pParams(array $params): string {
|
||||
if ($this->hasNodeWithComments($params) ||
|
||||
($this->hasParamWithAttributes($params) && !$this->phpVersion->supportsAttributes())
|
||||
) {
|
||||
return $this->pCommaSeparatedMultiline($params, $this->phpVersion->supportsTrailingCommaInParamList()) . $this->nl;
|
||||
}
|
||||
return $this->pCommaSeparated($params);
|
||||
}
|
||||
|
||||
/** @param Node\AttributeGroup[] $nodes */
|
||||
protected function pAttrGroups(array $nodes, bool $inline = false): string {
|
||||
$result = '';
|
||||
|
@@ -14,6 +14,7 @@ use PhpParser\Node\Expr\Cast;
|
||||
use PhpParser\Node\IntersectionType;
|
||||
use PhpParser\Node\MatchArm;
|
||||
use PhpParser\Node\Param;
|
||||
use PhpParser\Node\PropertyHook;
|
||||
use PhpParser\Node\Scalar;
|
||||
use PhpParser\Node\Stmt;
|
||||
use PhpParser\Node\UnionType;
|
||||
@@ -58,9 +59,11 @@ abstract class PrettyPrinterAbstract implements PrettyPrinter {
|
||||
BinaryOp\Mod::class => [ 40, 41, 40],
|
||||
BinaryOp\Plus::class => [ 50, 51, 50],
|
||||
BinaryOp\Minus::class => [ 50, 51, 50],
|
||||
// FIXME: This precedence is incorrect for PHP 8.
|
||||
BinaryOp\Concat::class => [ 50, 51, 50],
|
||||
BinaryOp\ShiftLeft::class => [ 60, 61, 60],
|
||||
BinaryOp\ShiftRight::class => [ 60, 61, 60],
|
||||
BinaryOp\Pipe::class => [ 65, 66, 65],
|
||||
BinaryOp\Smaller::class => [ 70, 70, 70],
|
||||
BinaryOp\SmallerOrEqual::class => [ 70, 70, 70],
|
||||
BinaryOp\Greater::class => [ 70, 70, 70],
|
||||
@@ -76,7 +79,7 @@ abstract class PrettyPrinterAbstract implements PrettyPrinter {
|
||||
BinaryOp\BooleanAnd::class => [120, 121, 120],
|
||||
BinaryOp\BooleanOr::class => [130, 131, 130],
|
||||
BinaryOp\Coalesce::class => [140, 140, 141],
|
||||
Expr\Ternary::class => [150, -1, -1],
|
||||
Expr\Ternary::class => [150, 150, 150],
|
||||
Expr\Assign::class => [160, -1, -1],
|
||||
Expr\AssignRef::class => [160, -1, -1],
|
||||
AssignOp\Plus::class => [160, -1, -1],
|
||||
@@ -101,10 +104,20 @@ abstract class PrettyPrinterAbstract implements PrettyPrinter {
|
||||
Expr\Include_::class => [220, -1, -1],
|
||||
Expr\ArrowFunction::class => [230, -1, -1],
|
||||
Expr\Throw_::class => [240, -1, -1],
|
||||
Expr\Cast\Void_::class => [250, -1, -1],
|
||||
];
|
||||
|
||||
/** @var int Current indentation level. */
|
||||
protected int $indentLevel;
|
||||
/** @var string String for single level of indentation */
|
||||
private string $indent;
|
||||
/** @var int Width in spaces to indent by. */
|
||||
private int $indentWidth;
|
||||
/** @var bool Whether to use tab indentation. */
|
||||
private bool $useTabs;
|
||||
/** @var int Width in spaces of one tab. */
|
||||
private int $tabWidth = 4;
|
||||
|
||||
/** @var string Newline style. Does not include current indentation. */
|
||||
protected string $newline;
|
||||
/** @var string Newline including current indentation. */
|
||||
@@ -169,12 +182,14 @@ abstract class PrettyPrinterAbstract implements PrettyPrinter {
|
||||
* PHP version while specifying an older target (but the result will
|
||||
* of course not be compatible with the older version in that case).
|
||||
* * string $newline: The newline style to use. Should be "\n" (default) or "\r\n".
|
||||
* * string $indent: The indentation to use. Should either be all spaces or a single
|
||||
* tab. Defaults to four spaces (" ").
|
||||
* * bool $shortArraySyntax: Whether to use [] instead of array() as the default array
|
||||
* syntax, if the node does not specify a format. Defaults to whether
|
||||
* the phpVersion support short array syntax.
|
||||
*
|
||||
* @param array{
|
||||
* phpVersion?: PhpVersion, newline?: string, shortArraySyntax?: bool
|
||||
* phpVersion?: PhpVersion, newline?: string, indent?: string, shortArraySyntax?: bool
|
||||
* } $options Dictionary of formatting options
|
||||
*/
|
||||
public function __construct(array $options = []) {
|
||||
@@ -189,6 +204,17 @@ abstract class PrettyPrinterAbstract implements PrettyPrinter {
|
||||
$options['shortArraySyntax'] ?? $this->phpVersion->supportsShortArraySyntax();
|
||||
$this->docStringEndToken =
|
||||
$this->phpVersion->supportsFlexibleHeredoc() ? null : '_DOC_STRING_END_' . mt_rand();
|
||||
|
||||
$this->indent = $indent = $options['indent'] ?? ' ';
|
||||
if ($indent === "\t") {
|
||||
$this->useTabs = true;
|
||||
$this->indentWidth = $this->tabWidth;
|
||||
} elseif ($indent === \str_repeat(' ', \strlen($indent))) {
|
||||
$this->useTabs = false;
|
||||
$this->indentWidth = \strlen($indent);
|
||||
} else {
|
||||
throw new \LogicException('Option "indent" must either be all spaces or a single tab');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -207,24 +233,29 @@ abstract class PrettyPrinterAbstract implements PrettyPrinter {
|
||||
*/
|
||||
protected function setIndentLevel(int $level): void {
|
||||
$this->indentLevel = $level;
|
||||
$this->nl = $this->newline . \str_repeat(' ', $level);
|
||||
if ($this->useTabs) {
|
||||
$tabs = \intdiv($level, $this->tabWidth);
|
||||
$spaces = $level % $this->tabWidth;
|
||||
$this->nl = $this->newline . \str_repeat("\t", $tabs) . \str_repeat(' ', $spaces);
|
||||
} else {
|
||||
$this->nl = $this->newline . \str_repeat(' ', $level);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Increase indentation level.
|
||||
*/
|
||||
protected function indent(): void {
|
||||
$this->indentLevel += 4;
|
||||
$this->nl .= ' ';
|
||||
$this->indentLevel += $this->indentWidth;
|
||||
$this->nl .= $this->indent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrease indentation level.
|
||||
*/
|
||||
protected function outdent(): void {
|
||||
assert($this->indentLevel >= 4);
|
||||
$this->indentLevel -= 4;
|
||||
$this->nl = $this->newline . str_repeat(' ', $this->indentLevel);
|
||||
assert($this->indentLevel >= $this->indentWidth);
|
||||
$this->setIndentLevel($this->indentLevel - $this->indentWidth);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -536,7 +567,7 @@ abstract class PrettyPrinterAbstract implements PrettyPrinter {
|
||||
$this->initializeModifierChangeMap();
|
||||
|
||||
$this->resetState();
|
||||
$this->origTokens = new TokenStream($origTokens);
|
||||
$this->origTokens = new TokenStream($origTokens, $this->tabWidth);
|
||||
|
||||
$this->preprocessNodes($stmts);
|
||||
|
||||
@@ -708,7 +739,7 @@ abstract class PrettyPrinterAbstract implements PrettyPrinter {
|
||||
$result .= $extraLeft;
|
||||
|
||||
$origIndentLevel = $this->indentLevel;
|
||||
$this->setIndentLevel($this->origTokens->getIndentationBefore($subStartPos) + $indentAdjustment);
|
||||
$this->setIndentLevel(max($this->origTokens->getIndentationBefore($subStartPos) + $indentAdjustment, 0));
|
||||
|
||||
// If it's the same node that was previously in this position, it certainly doesn't
|
||||
// need fixup. It's important to check this here, because our fixup checks are more
|
||||
@@ -811,7 +842,7 @@ abstract class PrettyPrinterAbstract implements PrettyPrinter {
|
||||
\assert($itemStartPos >= 0 && $itemEndPos >= 0 && $itemStartPos >= $pos);
|
||||
|
||||
$origIndentLevel = $this->indentLevel;
|
||||
$lastElemIndentLevel = $this->origTokens->getIndentationBefore($itemStartPos) + $indentAdjustment;
|
||||
$lastElemIndentLevel = max($this->origTokens->getIndentationBefore($itemStartPos) + $indentAdjustment, 0);
|
||||
$this->setIndentLevel($lastElemIndentLevel);
|
||||
|
||||
$comments = $arrItem->getComments();
|
||||
@@ -826,9 +857,8 @@ abstract class PrettyPrinterAbstract implements PrettyPrinter {
|
||||
}
|
||||
|
||||
if ($skipRemovedNode) {
|
||||
if ($isStmtList && ($this->origTokens->haveBracesInRange($pos, $itemStartPos) ||
|
||||
$this->origTokens->haveTagInRange($pos, $itemStartPos))) {
|
||||
// We'd remove the brace of a code block.
|
||||
if ($isStmtList && $this->origTokens->haveTagInRange($pos, $itemStartPos)) {
|
||||
// We'd remove an opening/closing PHP tag.
|
||||
// TODO: Preserve formatting.
|
||||
$this->setIndentLevel($origIndentLevel);
|
||||
return null;
|
||||
@@ -937,9 +967,8 @@ abstract class PrettyPrinterAbstract implements PrettyPrinter {
|
||||
$pos, $itemStartPos, $indentAdjustment);
|
||||
$skipRemovedNode = true;
|
||||
} else {
|
||||
if ($isStmtList && ($this->origTokens->haveBracesInRange($pos, $itemStartPos) ||
|
||||
$this->origTokens->haveTagInRange($pos, $itemStartPos))) {
|
||||
// We'd remove the brace of a code block.
|
||||
if ($isStmtList && $this->origTokens->haveTagInRange($pos, $itemStartPos)) {
|
||||
// We'd remove an opening/closing PHP tag.
|
||||
// TODO: Preserve formatting.
|
||||
return null;
|
||||
}
|
||||
@@ -1197,6 +1226,9 @@ abstract class PrettyPrinterAbstract implements PrettyPrinter {
|
||||
. ($modifiers & Modifiers::PUBLIC ? 'public ' : '')
|
||||
. ($modifiers & Modifiers::PROTECTED ? 'protected ' : '')
|
||||
. ($modifiers & Modifiers::PRIVATE ? 'private ' : '')
|
||||
. ($modifiers & Modifiers::PUBLIC_SET ? 'public(set) ' : '')
|
||||
. ($modifiers & Modifiers::PROTECTED_SET ? 'protected(set) ' : '')
|
||||
. ($modifiers & Modifiers::PRIVATE_SET ? 'private(set) ' : '')
|
||||
. ($modifiers & Modifiers::STATIC ? 'static ' : '')
|
||||
. ($modifiers & Modifiers::READONLY ? 'readonly ' : '');
|
||||
}
|
||||
@@ -1343,7 +1375,7 @@ abstract class PrettyPrinterAbstract implements PrettyPrinter {
|
||||
BinaryOp\NotIdentical::class, BinaryOp\Spaceship::class, BinaryOp\BitwiseAnd::class,
|
||||
BinaryOp\BitwiseXor::class, BinaryOp\BitwiseOr::class, BinaryOp\BooleanAnd::class,
|
||||
BinaryOp\BooleanOr::class, BinaryOp\Coalesce::class, BinaryOp\LogicalAnd::class,
|
||||
BinaryOp\LogicalXor::class, BinaryOp\LogicalOr::class,
|
||||
BinaryOp\LogicalXor::class, BinaryOp\LogicalOr::class, BinaryOp\Pipe::class,
|
||||
];
|
||||
foreach ($binaryOps as $binaryOp) {
|
||||
$this->fixupMap[$binaryOp] = [
|
||||
@@ -1519,6 +1551,7 @@ abstract class PrettyPrinterAbstract implements PrettyPrinter {
|
||||
Stmt\UseUse::class . '->uses' => ', ',
|
||||
MatchArm::class . '->conds' => ', ',
|
||||
AttributeGroup::class . '->attrs' => ', ',
|
||||
PropertyHook::class . '->params' => ', ',
|
||||
|
||||
// statement lists
|
||||
Expr\Closure::class . '->stmts' => "\n",
|
||||
@@ -1540,6 +1573,7 @@ abstract class PrettyPrinterAbstract implements PrettyPrinter {
|
||||
Stmt\Function_::class . '->stmts' => "\n",
|
||||
Stmt\If_::class . '->stmts' => "\n",
|
||||
Stmt\Namespace_::class . '->stmts' => "\n",
|
||||
Stmt\Block::class . '->stmts' => "\n",
|
||||
|
||||
// Attribute groups
|
||||
Stmt\Class_::class . '->attrGroups' => "\n",
|
||||
@@ -1555,10 +1589,15 @@ abstract class PrettyPrinterAbstract implements PrettyPrinter {
|
||||
Expr\Closure::class . '->attrGroups' => ' ',
|
||||
Expr\ArrowFunction::class . '->attrGroups' => ' ',
|
||||
Param::class . '->attrGroups' => ' ',
|
||||
PropertyHook::class . '->attrGroups' => ' ',
|
||||
|
||||
Stmt\Switch_::class . '->cases' => "\n",
|
||||
Stmt\TraitUse::class . '->adaptations' => "\n",
|
||||
Stmt\TryCatch::class . '->stmts' => "\n",
|
||||
Stmt\While_::class . '->stmts' => "\n",
|
||||
PropertyHook::class . '->body' => "\n",
|
||||
Stmt\Property::class . '->hooks' => "\n",
|
||||
Param::class . '->hooks' => "\n",
|
||||
|
||||
// dummy for top-level context
|
||||
'File->stmts' => "\n",
|
||||
@@ -1598,6 +1637,7 @@ abstract class PrettyPrinterAbstract implements PrettyPrinter {
|
||||
Stmt\Trait_::class . '->attrGroups' => [null, '', "\n"],
|
||||
Expr\ArrowFunction::class . '->attrGroups' => [null, '', ' '],
|
||||
Expr\Closure::class . '->attrGroups' => [null, '', ' '],
|
||||
Stmt\Const_::class . '->attrGroups' => [null, '', "\n"],
|
||||
PrintableNewAnonClassNode::class . '->attrGroups' => [\T_NEW, ' ', ''],
|
||||
|
||||
/* These cannot be empty to start with:
|
||||
@@ -1642,6 +1682,7 @@ abstract class PrettyPrinterAbstract implements PrettyPrinter {
|
||||
Stmt\Property::class . '->flags' => ['pModifiers', \T_VARIABLE],
|
||||
PrintableNewAnonClassNode::class . '->flags' => ['pModifiers', \T_CLASS],
|
||||
Param::class . '->flags' => ['pModifiers', \T_VARIABLE],
|
||||
PropertyHook::class . '->flags' => ['pModifiers', \T_STRING],
|
||||
Expr\Closure::class . '->static' => ['pStatic', \T_FUNCTION],
|
||||
Expr\ArrowFunction::class . '->static' => ['pStatic', \T_FN],
|
||||
//Stmt\TraitUseAdaptation\Alias::class . '->newModifier' => 0, // TODO
|
||||
|
@@ -17,6 +17,14 @@ if (!\function_exists('PhpParser\defineCompatibilityTokens')) {
|
||||
'T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG',
|
||||
'T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG',
|
||||
'T_READONLY',
|
||||
// PHP 8.4
|
||||
'T_PROPERTY_C',
|
||||
'T_PUBLIC_SET',
|
||||
'T_PROTECTED_SET',
|
||||
'T_PRIVATE_SET',
|
||||
// PHP 8.5
|
||||
'T_PIPE',
|
||||
'T_VOID_CAST',
|
||||
];
|
||||
|
||||
// PHP-Parser might be used together with another library that also emulates some or all
|
||||
@@ -26,6 +34,13 @@ if (!\function_exists('PhpParser\defineCompatibilityTokens')) {
|
||||
foreach ($compatTokens as $token) {
|
||||
if (\defined($token)) {
|
||||
$tokenId = \constant($token);
|
||||
if (!\is_int($tokenId)) {
|
||||
throw new \Error(sprintf(
|
||||
'Token %s has ID of type %s, should be int. ' .
|
||||
'You may be using a library with broken token emulation',
|
||||
$token, \gettype($tokenId)
|
||||
));
|
||||
}
|
||||
$clashingToken = $usedTokenIds[$tokenId] ?? null;
|
||||
if ($clashingToken !== null) {
|
||||
throw new \Error(sprintf(
|
||||
|
@@ -1,241 +1,271 @@
|
||||
parameters:
|
||||
ignoreErrors:
|
||||
-
|
||||
message: "#^Method PhpParser\\\\Builder\\\\ClassConst\\:\\:__construct\\(\\) has parameter \\$value with no value type specified in iterable type array\\.$#"
|
||||
message: '#^Method PhpParser\\Builder\\ClassConst\:\:__construct\(\) has parameter \$value with no value type specified in iterable type array\.$#'
|
||||
identifier: missingType.iterableValue
|
||||
count: 1
|
||||
path: lib/PhpParser/Builder/ClassConst.php
|
||||
|
||||
-
|
||||
message: "#^Method PhpParser\\\\Builder\\\\ClassConst\\:\\:addConst\\(\\) has parameter \\$value with no value type specified in iterable type array\\.$#"
|
||||
message: '#^Method PhpParser\\Builder\\ClassConst\:\:addConst\(\) has parameter \$value with no value type specified in iterable type array\.$#'
|
||||
identifier: missingType.iterableValue
|
||||
count: 1
|
||||
path: lib/PhpParser/Builder/ClassConst.php
|
||||
|
||||
-
|
||||
message: "#^Method PhpParser\\\\BuilderFactory\\:\\:args\\(\\) has parameter \\$args with no value type specified in iterable type array\\.$#"
|
||||
message: '#^Method PhpParser\\BuilderFactory\:\:args\(\) has parameter \$args with no value type specified in iterable type array\.$#'
|
||||
identifier: missingType.iterableValue
|
||||
count: 1
|
||||
path: lib/PhpParser/BuilderFactory.php
|
||||
|
||||
-
|
||||
message: "#^Method PhpParser\\\\BuilderFactory\\:\\:attribute\\(\\) has parameter \\$args with no value type specified in iterable type array\\.$#"
|
||||
message: '#^Method PhpParser\\BuilderFactory\:\:attribute\(\) has parameter \$args with no value type specified in iterable type array\.$#'
|
||||
identifier: missingType.iterableValue
|
||||
count: 1
|
||||
path: lib/PhpParser/BuilderFactory.php
|
||||
|
||||
-
|
||||
message: "#^Method PhpParser\\\\BuilderFactory\\:\\:classConst\\(\\) has parameter \\$value with no value type specified in iterable type array\\.$#"
|
||||
message: '#^Method PhpParser\\BuilderFactory\:\:classConst\(\) has parameter \$value with no value type specified in iterable type array\.$#'
|
||||
identifier: missingType.iterableValue
|
||||
count: 1
|
||||
path: lib/PhpParser/BuilderFactory.php
|
||||
|
||||
-
|
||||
message: "#^Method PhpParser\\\\BuilderFactory\\:\\:funcCall\\(\\) has parameter \\$args with no value type specified in iterable type array\\.$#"
|
||||
message: '#^Method PhpParser\\BuilderFactory\:\:funcCall\(\) has parameter \$args with no value type specified in iterable type array\.$#'
|
||||
identifier: missingType.iterableValue
|
||||
count: 1
|
||||
path: lib/PhpParser/BuilderFactory.php
|
||||
|
||||
-
|
||||
message: "#^Method PhpParser\\\\BuilderFactory\\:\\:methodCall\\(\\) has parameter \\$args with no value type specified in iterable type array\\.$#"
|
||||
message: '#^Method PhpParser\\BuilderFactory\:\:methodCall\(\) has parameter \$args with no value type specified in iterable type array\.$#'
|
||||
identifier: missingType.iterableValue
|
||||
count: 1
|
||||
path: lib/PhpParser/BuilderFactory.php
|
||||
|
||||
-
|
||||
message: "#^Method PhpParser\\\\BuilderFactory\\:\\:new\\(\\) has parameter \\$args with no value type specified in iterable type array\\.$#"
|
||||
message: '#^Method PhpParser\\BuilderFactory\:\:new\(\) has parameter \$args with no value type specified in iterable type array\.$#'
|
||||
identifier: missingType.iterableValue
|
||||
count: 1
|
||||
path: lib/PhpParser/BuilderFactory.php
|
||||
|
||||
-
|
||||
message: "#^Method PhpParser\\\\BuilderFactory\\:\\:staticCall\\(\\) has parameter \\$args with no value type specified in iterable type array\\.$#"
|
||||
message: '#^Method PhpParser\\BuilderFactory\:\:staticCall\(\) has parameter \$args with no value type specified in iterable type array\.$#'
|
||||
identifier: missingType.iterableValue
|
||||
count: 1
|
||||
path: lib/PhpParser/BuilderFactory.php
|
||||
|
||||
-
|
||||
message: "#^Method PhpParser\\\\BuilderFactory\\:\\:val\\(\\) has parameter \\$value with no value type specified in iterable type array\\.$#"
|
||||
message: '#^Method PhpParser\\BuilderFactory\:\:val\(\) has parameter \$value with no value type specified in iterable type array\.$#'
|
||||
identifier: missingType.iterableValue
|
||||
count: 1
|
||||
path: lib/PhpParser/BuilderFactory.php
|
||||
|
||||
-
|
||||
message: "#^Method PhpParser\\\\BuilderHelpers\\:\\:normalizeValue\\(\\) has parameter \\$value with no value type specified in iterable type array\\.$#"
|
||||
message: '#^Method PhpParser\\BuilderHelpers\:\:normalizeValue\(\) has parameter \$value with no value type specified in iterable type array\.$#'
|
||||
identifier: missingType.iterableValue
|
||||
count: 1
|
||||
path: lib/PhpParser/BuilderHelpers.php
|
||||
|
||||
-
|
||||
message: "#^Method PhpParser\\\\ConstExprEvaluator\\:\\:evaluateArray\\(\\) return type has no value type specified in iterable type array\\.$#"
|
||||
message: '#^Method PhpParser\\ConstExprEvaluator\:\:evaluateArray\(\) return type has no value type specified in iterable type array\.$#'
|
||||
identifier: missingType.iterableValue
|
||||
count: 1
|
||||
path: lib/PhpParser/ConstExprEvaluator.php
|
||||
|
||||
-
|
||||
message: "#^Unary operation \"~\" on mixed results in an error\\.$#"
|
||||
count: 1
|
||||
path: lib/PhpParser/ConstExprEvaluator.php
|
||||
|
||||
-
|
||||
message: "#^Method PhpParser\\\\JsonDecoder\\:\\:decodeArray\\(\\) has parameter \\$array with no value type specified in iterable type array\\.$#"
|
||||
message: '#^Method PhpParser\\JsonDecoder\:\:decodeArray\(\) has parameter \$array with no value type specified in iterable type array\.$#'
|
||||
identifier: missingType.iterableValue
|
||||
count: 1
|
||||
path: lib/PhpParser/JsonDecoder.php
|
||||
|
||||
-
|
||||
message: "#^Method PhpParser\\\\JsonDecoder\\:\\:decodeArray\\(\\) return type has no value type specified in iterable type array\\.$#"
|
||||
message: '#^Method PhpParser\\JsonDecoder\:\:decodeArray\(\) return type has no value type specified in iterable type array\.$#'
|
||||
identifier: missingType.iterableValue
|
||||
count: 1
|
||||
path: lib/PhpParser/JsonDecoder.php
|
||||
|
||||
-
|
||||
message: "#^Method PhpParser\\\\JsonDecoder\\:\\:decodeComment\\(\\) has parameter \\$value with no value type specified in iterable type array\\.$#"
|
||||
message: '#^Method PhpParser\\JsonDecoder\:\:decodeComment\(\) has parameter \$value with no value type specified in iterable type array\.$#'
|
||||
identifier: missingType.iterableValue
|
||||
count: 1
|
||||
path: lib/PhpParser/JsonDecoder.php
|
||||
|
||||
-
|
||||
message: "#^Method PhpParser\\\\JsonDecoder\\:\\:decodeNode\\(\\) has parameter \\$value with no value type specified in iterable type array\\.$#"
|
||||
message: '#^Method PhpParser\\JsonDecoder\:\:decodeNode\(\) has parameter \$value with no value type specified in iterable type array\.$#'
|
||||
identifier: missingType.iterableValue
|
||||
count: 1
|
||||
path: lib/PhpParser/JsonDecoder.php
|
||||
|
||||
-
|
||||
message: "#^Call to function assert\\(\\) with false will always evaluate to false\\.$#"
|
||||
message: '#^Call to function assert\(\) with false will always evaluate to false\.$#'
|
||||
identifier: function.impossibleType
|
||||
count: 1
|
||||
path: lib/PhpParser/Lexer/Emulative.php
|
||||
|
||||
-
|
||||
message: "#^Method PhpParser\\\\NodeDumper\\:\\:__construct\\(\\) has parameter \\$options with no value type specified in iterable type array\\.$#"
|
||||
message: '#^Constant T_PIPE not found\.$#'
|
||||
identifier: constant.notFound
|
||||
count: 2
|
||||
path: lib/PhpParser/Lexer/TokenEmulator/PipeOperatorEmulator.php
|
||||
|
||||
-
|
||||
message: '#^Constant T_VOID_CAST not found\.$#'
|
||||
identifier: constant.notFound
|
||||
count: 2
|
||||
path: lib/PhpParser/Lexer/TokenEmulator/VoidCastEmulator.php
|
||||
|
||||
-
|
||||
message: '#^If condition is always false\.$#'
|
||||
identifier: if.alwaysFalse
|
||||
count: 1
|
||||
path: lib/PhpParser/Node/Expr/ArrayItem.php
|
||||
|
||||
-
|
||||
message: '#^If condition is always false\.$#'
|
||||
identifier: if.alwaysFalse
|
||||
count: 1
|
||||
path: lib/PhpParser/Node/Expr/ClosureUse.php
|
||||
|
||||
-
|
||||
message: '#^If condition is always false\.$#'
|
||||
identifier: if.alwaysFalse
|
||||
count: 1
|
||||
path: lib/PhpParser/Node/Scalar/DNumber.php
|
||||
|
||||
-
|
||||
message: '#^If condition is always false\.$#'
|
||||
identifier: if.alwaysFalse
|
||||
count: 1
|
||||
path: lib/PhpParser/Node/Scalar/Encapsed.php
|
||||
|
||||
-
|
||||
message: '#^If condition is always false\.$#'
|
||||
identifier: if.alwaysFalse
|
||||
count: 1
|
||||
path: lib/PhpParser/Node/Scalar/EncapsedStringPart.php
|
||||
|
||||
-
|
||||
message: '#^If condition is always false\.$#'
|
||||
identifier: if.alwaysFalse
|
||||
count: 1
|
||||
path: lib/PhpParser/Node/Scalar/LNumber.php
|
||||
|
||||
-
|
||||
message: '#^If condition is always false\.$#'
|
||||
identifier: if.alwaysFalse
|
||||
count: 1
|
||||
path: lib/PhpParser/Node/Stmt/DeclareDeclare.php
|
||||
|
||||
-
|
||||
message: '#^If condition is always false\.$#'
|
||||
identifier: if.alwaysFalse
|
||||
count: 1
|
||||
path: lib/PhpParser/Node/Stmt/PropertyProperty.php
|
||||
|
||||
-
|
||||
message: '#^If condition is always false\.$#'
|
||||
identifier: if.alwaysFalse
|
||||
count: 1
|
||||
path: lib/PhpParser/Node/Stmt/StaticVar.php
|
||||
|
||||
-
|
||||
message: '#^If condition is always false\.$#'
|
||||
identifier: if.alwaysFalse
|
||||
count: 1
|
||||
path: lib/PhpParser/Node/Stmt/UseUse.php
|
||||
|
||||
-
|
||||
message: '#^Method PhpParser\\NodeDumper\:\:__construct\(\) has parameter \$options with no value type specified in iterable type array\.$#'
|
||||
identifier: missingType.iterableValue
|
||||
count: 1
|
||||
path: lib/PhpParser/NodeDumper.php
|
||||
|
||||
-
|
||||
message: "#^Method PhpParser\\\\NodeDumper\\:\\:dump\\(\\) has parameter \\$node with no value type specified in iterable type array\\.$#"
|
||||
message: '#^Method PhpParser\\NodeDumper\:\:dump\(\) has parameter \$node with no value type specified in iterable type array\.$#'
|
||||
identifier: missingType.iterableValue
|
||||
count: 1
|
||||
path: lib/PhpParser/NodeDumper.php
|
||||
|
||||
-
|
||||
message: "#^Method PhpParser\\\\NodeDumper\\:\\:dumpRecursive\\(\\) has parameter \\$node with no value type specified in iterable type array\\.$#"
|
||||
count: 1
|
||||
path: lib/PhpParser/NodeDumper.php
|
||||
|
||||
-
|
||||
message: "#^Method PhpParser\\\\NodeTraverser\\:\\:traverseArray\\(\\) has parameter \\$nodes with no value type specified in iterable type array\\.$#"
|
||||
message: '#^Method PhpParser\\NodeTraverser\:\:traverseArray\(\) has parameter \$nodes with no value type specified in iterable type array\.$#'
|
||||
identifier: missingType.iterableValue
|
||||
count: 1
|
||||
path: lib/PhpParser/NodeTraverser.php
|
||||
|
||||
-
|
||||
message: "#^Method PhpParser\\\\NodeTraverser\\:\\:traverseArray\\(\\) return type has no value type specified in iterable type array\\.$#"
|
||||
message: '#^Method PhpParser\\NodeTraverser\:\:traverseArray\(\) return type has no value type specified in iterable type array\.$#'
|
||||
identifier: missingType.iterableValue
|
||||
count: 1
|
||||
path: lib/PhpParser/NodeTraverser.php
|
||||
|
||||
-
|
||||
message: "#^Access to an undefined property PhpParser\\\\Node\\:\\:\\$attrGroups\\.$#"
|
||||
message: '#^Access to an undefined property PhpParser\\Node\:\:\$attrGroups\.$#'
|
||||
identifier: property.notFound
|
||||
count: 1
|
||||
path: lib/PhpParser/NodeVisitor/NameResolver.php
|
||||
|
||||
-
|
||||
message: "#^Access to an undefined property PhpParser\\\\Node\\:\\:\\$name\\.$#"
|
||||
message: '#^Access to an undefined property PhpParser\\Node\:\:\$name\.$#'
|
||||
identifier: property.notFound
|
||||
count: 1
|
||||
path: lib/PhpParser/NodeVisitor/NameResolver.php
|
||||
|
||||
-
|
||||
message: "#^Access to an undefined property PhpParser\\\\Node\\:\\:\\$namespacedName\\.$#"
|
||||
message: '#^Access to an undefined property PhpParser\\Node\:\:\$namespacedName\.$#'
|
||||
identifier: property.notFound
|
||||
count: 1
|
||||
path: lib/PhpParser/NodeVisitor/NameResolver.php
|
||||
|
||||
-
|
||||
message: "#^Method PhpParser\\\\NodeVisitor\\\\NodeConnectingVisitor\\:\\:beforeTraverse\\(\\) should return array\\<PhpParser\\\\Node\\>\\|null but return statement is missing\\.$#"
|
||||
message: '#^Method PhpParser\\NodeVisitor\\NodeConnectingVisitor\:\:beforeTraverse\(\) should return array\<PhpParser\\Node\>\|null but return statement is missing\.$#'
|
||||
identifier: return.missing
|
||||
count: 1
|
||||
path: lib/PhpParser/NodeVisitor/NodeConnectingVisitor.php
|
||||
|
||||
-
|
||||
message: "#^Method PhpParser\\\\NodeVisitor\\\\NodeConnectingVisitor\\:\\:enterNode\\(\\) should return array\\<PhpParser\\\\Node\\>\\|int\\|PhpParser\\\\Node\\|null but return statement is missing\\.$#"
|
||||
message: '#^Method PhpParser\\NodeVisitor\\NodeConnectingVisitor\:\:enterNode\(\) should return array\<PhpParser\\Node\>\|int\|PhpParser\\Node\|null but return statement is missing\.$#'
|
||||
identifier: return.missing
|
||||
count: 1
|
||||
path: lib/PhpParser/NodeVisitor/NodeConnectingVisitor.php
|
||||
|
||||
-
|
||||
message: "#^Method PhpParser\\\\NodeVisitor\\\\NodeConnectingVisitor\\:\\:leaveNode\\(\\) should return array\\<PhpParser\\\\Node\\>\\|int\\|PhpParser\\\\Node\\|null but return statement is missing\\.$#"
|
||||
message: '#^Method PhpParser\\NodeVisitor\\NodeConnectingVisitor\:\:leaveNode\(\) should return array\<PhpParser\\Node\>\|int\|PhpParser\\Node\|null but return statement is missing\.$#'
|
||||
identifier: return.missing
|
||||
count: 1
|
||||
path: lib/PhpParser/NodeVisitor/NodeConnectingVisitor.php
|
||||
|
||||
-
|
||||
message: "#^Method PhpParser\\\\NodeVisitor\\\\ParentConnectingVisitor\\:\\:beforeTraverse\\(\\) should return array\\<PhpParser\\\\Node\\>\\|null but return statement is missing\\.$#"
|
||||
message: '#^Method PhpParser\\NodeVisitor\\ParentConnectingVisitor\:\:beforeTraverse\(\) should return array\<PhpParser\\Node\>\|null but return statement is missing\.$#'
|
||||
identifier: return.missing
|
||||
count: 1
|
||||
path: lib/PhpParser/NodeVisitor/ParentConnectingVisitor.php
|
||||
|
||||
-
|
||||
message: "#^Method PhpParser\\\\NodeVisitor\\\\ParentConnectingVisitor\\:\\:enterNode\\(\\) should return array\\<PhpParser\\\\Node\\>\\|int\\|PhpParser\\\\Node\\|null but return statement is missing\\.$#"
|
||||
message: '#^Method PhpParser\\NodeVisitor\\ParentConnectingVisitor\:\:enterNode\(\) should return array\<PhpParser\\Node\>\|int\|PhpParser\\Node\|null but return statement is missing\.$#'
|
||||
identifier: return.missing
|
||||
count: 1
|
||||
path: lib/PhpParser/NodeVisitor/ParentConnectingVisitor.php
|
||||
|
||||
-
|
||||
message: "#^Method PhpParser\\\\NodeVisitor\\\\ParentConnectingVisitor\\:\\:leaveNode\\(\\) should return array\\<PhpParser\\\\Node\\>\\|int\\|PhpParser\\\\Node\\|null but return statement is missing\\.$#"
|
||||
message: '#^Method PhpParser\\NodeVisitor\\ParentConnectingVisitor\:\:leaveNode\(\) should return array\<PhpParser\\Node\>\|int\|PhpParser\\Node\|null but return statement is missing\.$#'
|
||||
identifier: return.missing
|
||||
count: 1
|
||||
path: lib/PhpParser/NodeVisitor/ParentConnectingVisitor.php
|
||||
|
||||
-
|
||||
message: "#^Access to undefined constant static\\(PhpParser\\\\ParserAbstract\\)\\:\\:T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG\\.$#"
|
||||
message: '#^Access to undefined constant static\(PhpParser\\ParserAbstract\)\:\:T_ECHO\.$#'
|
||||
identifier: classConstant.notFound
|
||||
count: 1
|
||||
path: lib/PhpParser/ParserAbstract.php
|
||||
|
||||
-
|
||||
message: "#^Access to undefined constant static\\(PhpParser\\\\ParserAbstract\\)\\:\\:T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG\\.$#"
|
||||
message: '#^Unary operation "\+" on string results in an error\.$#'
|
||||
identifier: unaryOp.invalid
|
||||
count: 1
|
||||
path: lib/PhpParser/ParserAbstract.php
|
||||
|
||||
-
|
||||
message: "#^Access to undefined constant static\\(PhpParser\\\\ParserAbstract\\)\\:\\:T_ATTRIBUTE\\.$#"
|
||||
count: 1
|
||||
path: lib/PhpParser/ParserAbstract.php
|
||||
|
||||
-
|
||||
message: "#^Access to undefined constant static\\(PhpParser\\\\ParserAbstract\\)\\:\\:T_COALESCE_EQUAL\\.$#"
|
||||
count: 1
|
||||
path: lib/PhpParser/ParserAbstract.php
|
||||
|
||||
-
|
||||
message: "#^Access to undefined constant static\\(PhpParser\\\\ParserAbstract\\)\\:\\:T_ECHO\\.$#"
|
||||
count: 1
|
||||
path: lib/PhpParser/ParserAbstract.php
|
||||
|
||||
-
|
||||
message: "#^Access to undefined constant static\\(PhpParser\\\\ParserAbstract\\)\\:\\:T_ENUM\\.$#"
|
||||
count: 1
|
||||
path: lib/PhpParser/ParserAbstract.php
|
||||
|
||||
-
|
||||
message: "#^Access to undefined constant static\\(PhpParser\\\\ParserAbstract\\)\\:\\:T_FN\\.$#"
|
||||
count: 1
|
||||
path: lib/PhpParser/ParserAbstract.php
|
||||
|
||||
-
|
||||
message: "#^Access to undefined constant static\\(PhpParser\\\\ParserAbstract\\)\\:\\:T_MATCH\\.$#"
|
||||
count: 1
|
||||
path: lib/PhpParser/ParserAbstract.php
|
||||
|
||||
-
|
||||
message: "#^Access to undefined constant static\\(PhpParser\\\\ParserAbstract\\)\\:\\:T_NAME_FULLY_QUALIFIED\\.$#"
|
||||
count: 1
|
||||
path: lib/PhpParser/ParserAbstract.php
|
||||
|
||||
-
|
||||
message: "#^Access to undefined constant static\\(PhpParser\\\\ParserAbstract\\)\\:\\:T_NAME_QUALIFIED\\.$#"
|
||||
count: 1
|
||||
path: lib/PhpParser/ParserAbstract.php
|
||||
|
||||
-
|
||||
message: "#^Access to undefined constant static\\(PhpParser\\\\ParserAbstract\\)\\:\\:T_NAME_RELATIVE\\.$#"
|
||||
count: 1
|
||||
path: lib/PhpParser/ParserAbstract.php
|
||||
|
||||
-
|
||||
message: "#^Access to undefined constant static\\(PhpParser\\\\ParserAbstract\\)\\:\\:T_NULLSAFE_OBJECT_OPERATOR\\.$#"
|
||||
count: 1
|
||||
path: lib/PhpParser/ParserAbstract.php
|
||||
|
||||
-
|
||||
message: "#^Access to undefined constant static\\(PhpParser\\\\ParserAbstract\\)\\:\\:T_PAAMAYIM_NEKUDOTAYIM\\.$#"
|
||||
count: 1
|
||||
path: lib/PhpParser/ParserAbstract.php
|
||||
|
||||
-
|
||||
message: "#^Access to undefined constant static\\(PhpParser\\\\ParserAbstract\\)\\:\\:T_READONLY\\.$#"
|
||||
count: 1
|
||||
path: lib/PhpParser/ParserAbstract.php
|
||||
|
||||
-
|
||||
message: "#^Unary operation \"\\+\" on string results in an error\\.$#"
|
||||
count: 1
|
||||
path: lib/PhpParser/ParserAbstract.php
|
||||
|
||||
-
|
||||
message: "#^Variable \\$action might not be defined\\.$#"
|
||||
message: '#^Variable \$action might not be defined\.$#'
|
||||
identifier: variable.undefined
|
||||
count: 1
|
||||
path: lib/PhpParser/ParserAbstract.php
|
||||
|
@@ -4,6 +4,7 @@
|
||||
xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
|
||||
backupGlobals="false"
|
||||
colors="true"
|
||||
convertDeprecationsToExceptions="true"
|
||||
beStrictAboutTestsThatDoNotTestAnything="false"
|
||||
bootstrap="./test/bootstrap.php">
|
||||
<testsuites>
|
||||
@@ -12,9 +13,9 @@
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
|
||||
<filter>
|
||||
<whitelist>
|
||||
<coverage>
|
||||
<include>
|
||||
<directory suffix=".php">./lib/PhpParser/</directory>
|
||||
</whitelist>
|
||||
</filter>
|
||||
</include>
|
||||
</coverage>
|
||||
</phpunit>
|
||||
|
@@ -20,7 +20,7 @@ class ClassConstTest extends \PHPUnit\Framework\TestCase {
|
||||
return new ClassConst($name, $value);
|
||||
}
|
||||
|
||||
public function testModifiers() {
|
||||
public function testModifiers(): void {
|
||||
$node = $this->createClassConstBuilder("TEST", 1)
|
||||
->makePrivate()
|
||||
->getNode()
|
||||
@@ -82,7 +82,7 @@ class ClassConstTest extends \PHPUnit\Framework\TestCase {
|
||||
);
|
||||
}
|
||||
|
||||
public function testDocComment() {
|
||||
public function testDocComment(): void {
|
||||
$node = $this->createClassConstBuilder('TEST', 1)
|
||||
->setDocComment('/** Test */')
|
||||
->makePublic()
|
||||
@@ -102,7 +102,7 @@ class ClassConstTest extends \PHPUnit\Framework\TestCase {
|
||||
);
|
||||
}
|
||||
|
||||
public function testAddConst() {
|
||||
public function testAddConst(): void {
|
||||
$node = $this->createClassConstBuilder('FIRST_TEST', 1)
|
||||
->addConst("SECOND_TEST", 2)
|
||||
->getNode();
|
||||
@@ -118,7 +118,7 @@ class ClassConstTest extends \PHPUnit\Framework\TestCase {
|
||||
);
|
||||
}
|
||||
|
||||
public function testAddAttribute() {
|
||||
public function testAddAttribute(): void {
|
||||
$attribute = new Attribute(
|
||||
new Name('Attr'),
|
||||
[new Arg(new Int_(1), false, false, [], new Identifier('name'))]
|
||||
@@ -142,7 +142,7 @@ class ClassConstTest extends \PHPUnit\Framework\TestCase {
|
||||
);
|
||||
}
|
||||
|
||||
public function testType() {
|
||||
public function testType(): void {
|
||||
$node = $this->createClassConstBuilder('TYPE', 1)
|
||||
->setType('int')
|
||||
->getNode();
|
||||
@@ -157,7 +157,7 @@ class ClassConstTest extends \PHPUnit\Framework\TestCase {
|
||||
/**
|
||||
* @dataProvider provideTestDefaultValues
|
||||
*/
|
||||
public function testValues($value, $expectedValueNode) {
|
||||
public function testValues($value, $expectedValueNode): void {
|
||||
$node = $this->createClassConstBuilder('TEST', $value)
|
||||
->getNode()
|
||||
;
|
||||
@@ -165,7 +165,7 @@ class ClassConstTest extends \PHPUnit\Framework\TestCase {
|
||||
$this->assertEquals($expectedValueNode, $node->consts[0]->value);
|
||||
}
|
||||
|
||||
public function provideTestDefaultValues() {
|
||||
public static function provideTestDefaultValues() {
|
||||
return [
|
||||
[
|
||||
null,
|
||||
|
@@ -18,7 +18,7 @@ class ClassTest extends \PHPUnit\Framework\TestCase {
|
||||
return new Class_($class);
|
||||
}
|
||||
|
||||
public function testExtendsImplements() {
|
||||
public function testExtendsImplements(): void {
|
||||
$node = $this->createClassBuilder('SomeLogger')
|
||||
->extend('BaseLogger')
|
||||
->implement('Namespaced\Logger', new Name('SomeInterface'))
|
||||
@@ -40,7 +40,7 @@ class ClassTest extends \PHPUnit\Framework\TestCase {
|
||||
);
|
||||
}
|
||||
|
||||
public function testAbstract() {
|
||||
public function testAbstract(): void {
|
||||
$node = $this->createClassBuilder('Test')
|
||||
->makeAbstract()
|
||||
->getNode()
|
||||
@@ -54,7 +54,7 @@ class ClassTest extends \PHPUnit\Framework\TestCase {
|
||||
);
|
||||
}
|
||||
|
||||
public function testFinal() {
|
||||
public function testFinal(): void {
|
||||
$node = $this->createClassBuilder('Test')
|
||||
->makeFinal()
|
||||
->getNode()
|
||||
@@ -68,7 +68,7 @@ class ClassTest extends \PHPUnit\Framework\TestCase {
|
||||
);
|
||||
}
|
||||
|
||||
public function testReadonly() {
|
||||
public function testReadonly(): void {
|
||||
$node = $this->createClassBuilder('Test')
|
||||
->makeReadonly()
|
||||
->getNode()
|
||||
@@ -82,7 +82,7 @@ class ClassTest extends \PHPUnit\Framework\TestCase {
|
||||
);
|
||||
}
|
||||
|
||||
public function testStatementOrder() {
|
||||
public function testStatementOrder(): void {
|
||||
$method = new Stmt\ClassMethod('testMethod');
|
||||
$property = new Stmt\Property(
|
||||
Modifiers::PUBLIC,
|
||||
@@ -108,7 +108,7 @@ class ClassTest extends \PHPUnit\Framework\TestCase {
|
||||
);
|
||||
}
|
||||
|
||||
public function testDocComment() {
|
||||
public function testDocComment(): void {
|
||||
$docComment = <<<'DOC'
|
||||
/**
|
||||
* Test
|
||||
@@ -141,7 +141,7 @@ DOC;
|
||||
);
|
||||
}
|
||||
|
||||
public function testAddAttribute() {
|
||||
public function testAddAttribute(): void {
|
||||
$attribute = new Attribute(
|
||||
new Name('Attr'),
|
||||
[new Arg(new Int_(1), false, false, [], new Identifier('name'))]
|
||||
@@ -162,7 +162,7 @@ DOC;
|
||||
);
|
||||
}
|
||||
|
||||
public function testInvalidStmtError() {
|
||||
public function testInvalidStmtError(): void {
|
||||
$this->expectException(\LogicException::class);
|
||||
$this->expectExceptionMessage('Unexpected node of type "Stmt_Echo"');
|
||||
$this->createClassBuilder('Test')
|
||||
@@ -170,21 +170,21 @@ DOC;
|
||||
;
|
||||
}
|
||||
|
||||
public function testInvalidDocComment() {
|
||||
public function testInvalidDocComment(): void {
|
||||
$this->expectException(\LogicException::class);
|
||||
$this->expectExceptionMessage('Doc comment must be a string or an instance of PhpParser\Comment\Doc');
|
||||
$this->createClassBuilder('Test')
|
||||
->setDocComment(new Comment('Test'));
|
||||
}
|
||||
|
||||
public function testEmptyName() {
|
||||
public function testEmptyName(): void {
|
||||
$this->expectException(\LogicException::class);
|
||||
$this->expectExceptionMessage('Name cannot be empty');
|
||||
$this->createClassBuilder('Test')
|
||||
->extend('');
|
||||
}
|
||||
|
||||
public function testInvalidName() {
|
||||
public function testInvalidName(): void {
|
||||
$this->expectException(\LogicException::class);
|
||||
$this->expectExceptionMessage('Name must be a string or an instance of Node\Name');
|
||||
$this->createClassBuilder('Test')
|
||||
|
@@ -17,7 +17,7 @@ class EnumCaseTest extends \PHPUnit\Framework\TestCase {
|
||||
return new EnumCase($name);
|
||||
}
|
||||
|
||||
public function testDocComment() {
|
||||
public function testDocComment(): void {
|
||||
$node = $this->createEnumCaseBuilder('TEST')
|
||||
->setDocComment('/** Test */')
|
||||
->getNode();
|
||||
@@ -35,7 +35,7 @@ class EnumCaseTest extends \PHPUnit\Framework\TestCase {
|
||||
);
|
||||
}
|
||||
|
||||
public function testAddAttribute() {
|
||||
public function testAddAttribute(): void {
|
||||
$attribute = new Attribute(
|
||||
new Name('Attr'),
|
||||
[new Arg(new Int_(1), false, false, [], new Identifier('name'))]
|
||||
@@ -59,7 +59,7 @@ class EnumCaseTest extends \PHPUnit\Framework\TestCase {
|
||||
/**
|
||||
* @dataProvider provideTestDefaultValues
|
||||
*/
|
||||
public function testValues($value, $expectedValueNode) {
|
||||
public function testValues($value, $expectedValueNode): void {
|
||||
$node = $this->createEnumCaseBuilder('TEST')
|
||||
->setValue($value)
|
||||
->getNode()
|
||||
@@ -68,7 +68,7 @@ class EnumCaseTest extends \PHPUnit\Framework\TestCase {
|
||||
$this->assertEquals($expectedValueNode, $node->expr);
|
||||
}
|
||||
|
||||
public function provideTestDefaultValues() {
|
||||
public static function provideTestDefaultValues() {
|
||||
return [
|
||||
[
|
||||
31415,
|
||||
|
@@ -17,7 +17,7 @@ class EnumTest extends \PHPUnit\Framework\TestCase {
|
||||
return new Enum_($class);
|
||||
}
|
||||
|
||||
public function testImplements() {
|
||||
public function testImplements(): void {
|
||||
$node = $this->createEnumBuilder('SomeEnum')
|
||||
->implement('Namespaced\SomeInterface', new Name('OtherInterface'))
|
||||
->getNode()
|
||||
@@ -34,7 +34,7 @@ class EnumTest extends \PHPUnit\Framework\TestCase {
|
||||
);
|
||||
}
|
||||
|
||||
public function testSetScalarType() {
|
||||
public function testSetScalarType(): void {
|
||||
$node = $this->createEnumBuilder('Test')
|
||||
->setScalarType('int')
|
||||
->getNode()
|
||||
@@ -48,7 +48,7 @@ class EnumTest extends \PHPUnit\Framework\TestCase {
|
||||
);
|
||||
}
|
||||
|
||||
public function testStatementOrder() {
|
||||
public function testStatementOrder(): void {
|
||||
$method = new Stmt\ClassMethod('testMethod');
|
||||
$enumCase = new Stmt\EnumCase(
|
||||
'TEST_ENUM_CASE'
|
||||
@@ -73,7 +73,7 @@ class EnumTest extends \PHPUnit\Framework\TestCase {
|
||||
);
|
||||
}
|
||||
|
||||
public function testDocComment() {
|
||||
public function testDocComment(): void {
|
||||
$docComment = <<<'DOC'
|
||||
/**
|
||||
* Test
|
||||
@@ -106,7 +106,7 @@ DOC;
|
||||
);
|
||||
}
|
||||
|
||||
public function testAddAttribute() {
|
||||
public function testAddAttribute(): void {
|
||||
$attribute = new Attribute(
|
||||
new Name('Attr'),
|
||||
[new Arg(new Int_(1), false, false, [], new Identifier('name'))]
|
||||
@@ -127,7 +127,7 @@ DOC;
|
||||
);
|
||||
}
|
||||
|
||||
public function testInvalidStmtError() {
|
||||
public function testInvalidStmtError(): void {
|
||||
$this->expectException(\LogicException::class);
|
||||
$this->expectExceptionMessage('Unexpected node of type "PropertyItem"');
|
||||
$this->createEnumBuilder('Test')
|
||||
@@ -135,21 +135,21 @@ DOC;
|
||||
;
|
||||
}
|
||||
|
||||
public function testInvalidDocComment() {
|
||||
public function testInvalidDocComment(): void {
|
||||
$this->expectException(\LogicException::class);
|
||||
$this->expectExceptionMessage('Doc comment must be a string or an instance of PhpParser\Comment\Doc');
|
||||
$this->createEnumBuilder('Test')
|
||||
->setDocComment(new Comment('Test'));
|
||||
}
|
||||
|
||||
public function testEmptyName() {
|
||||
public function testEmptyName(): void {
|
||||
$this->expectException(\LogicException::class);
|
||||
$this->expectExceptionMessage('Name cannot be empty');
|
||||
$this->createEnumBuilder('Test')
|
||||
->implement('');
|
||||
}
|
||||
|
||||
public function testInvalidName() {
|
||||
public function testInvalidName(): void {
|
||||
$this->expectException(\LogicException::class);
|
||||
$this->expectExceptionMessage('Name must be a string or an instance of Node\Name');
|
||||
$this->createEnumBuilder('Test')
|
||||
|
@@ -20,7 +20,7 @@ class FunctionTest extends \PHPUnit\Framework\TestCase {
|
||||
return new Function_($name);
|
||||
}
|
||||
|
||||
public function testReturnByRef() {
|
||||
public function testReturnByRef(): void {
|
||||
$node = $this->createFunctionBuilder('test')
|
||||
->makeReturnByRef()
|
||||
->getNode()
|
||||
@@ -34,7 +34,7 @@ class FunctionTest extends \PHPUnit\Framework\TestCase {
|
||||
);
|
||||
}
|
||||
|
||||
public function testParams() {
|
||||
public function testParams(): void {
|
||||
$param1 = new Node\Param(new Variable('test1'));
|
||||
$param2 = new Node\Param(new Variable('test2'));
|
||||
$param3 = new Node\Param(new Variable('test3'));
|
||||
@@ -53,7 +53,7 @@ class FunctionTest extends \PHPUnit\Framework\TestCase {
|
||||
);
|
||||
}
|
||||
|
||||
public function testStmts() {
|
||||
public function testStmts(): void {
|
||||
$stmt1 = new Print_(new String_('test1'));
|
||||
$stmt2 = new Print_(new String_('test2'));
|
||||
$stmt3 = new Print_(new String_('test3'));
|
||||
@@ -76,7 +76,7 @@ class FunctionTest extends \PHPUnit\Framework\TestCase {
|
||||
);
|
||||
}
|
||||
|
||||
public function testDocComment() {
|
||||
public function testDocComment(): void {
|
||||
$node = $this->createFunctionBuilder('test')
|
||||
->setDocComment('/** Test */')
|
||||
->getNode();
|
||||
@@ -86,7 +86,7 @@ class FunctionTest extends \PHPUnit\Framework\TestCase {
|
||||
]), $node);
|
||||
}
|
||||
|
||||
public function testAddAttribute() {
|
||||
public function testAddAttribute(): void {
|
||||
$attribute = new Attribute(
|
||||
new Name('Attr'),
|
||||
[new Arg(new Int_(1), false, false, [], new Identifier('name'))]
|
||||
@@ -102,7 +102,7 @@ class FunctionTest extends \PHPUnit\Framework\TestCase {
|
||||
], []), $node);
|
||||
}
|
||||
|
||||
public function testReturnType() {
|
||||
public function testReturnType(): void {
|
||||
$node = $this->createFunctionBuilder('test')
|
||||
->setReturnType('void')
|
||||
->getNode();
|
||||
@@ -112,13 +112,13 @@ class FunctionTest extends \PHPUnit\Framework\TestCase {
|
||||
], []), $node);
|
||||
}
|
||||
|
||||
public function testInvalidNullableVoidType() {
|
||||
public function testInvalidNullableVoidType(): void {
|
||||
$this->expectException(\LogicException::class);
|
||||
$this->expectExceptionMessage('void type cannot be nullable');
|
||||
$this->createFunctionBuilder('test')->setReturnType('?void');
|
||||
}
|
||||
|
||||
public function testInvalidParamError() {
|
||||
public function testInvalidParamError(): void {
|
||||
$this->expectException(\LogicException::class);
|
||||
$this->expectExceptionMessage('Expected parameter node, got "Name"');
|
||||
$this->createFunctionBuilder('test')
|
||||
@@ -126,7 +126,7 @@ class FunctionTest extends \PHPUnit\Framework\TestCase {
|
||||
;
|
||||
}
|
||||
|
||||
public function testAddNonStmt() {
|
||||
public function testAddNonStmt(): void {
|
||||
$this->expectException(\LogicException::class);
|
||||
$this->expectExceptionMessage('Expected statement or expression node');
|
||||
$this->createFunctionBuilder('test')
|
||||
|
@@ -23,13 +23,13 @@ class InterfaceTest extends \PHPUnit\Framework\TestCase {
|
||||
return $pp->prettyPrint([$node]);
|
||||
}
|
||||
|
||||
public function testEmpty() {
|
||||
public function testEmpty(): void {
|
||||
$contract = $this->createInterfaceBuilder()->getNode();
|
||||
$this->assertInstanceOf(Stmt\Interface_::class, $contract);
|
||||
$this->assertEquals(new Node\Identifier('Contract'), $contract->name);
|
||||
}
|
||||
|
||||
public function testExtending() {
|
||||
public function testExtending(): void {
|
||||
$contract = $this->createInterfaceBuilder()
|
||||
->extend('Space\Root1', 'Root2')->getNode();
|
||||
$this->assertEquals(
|
||||
@@ -42,13 +42,13 @@ class InterfaceTest extends \PHPUnit\Framework\TestCase {
|
||||
);
|
||||
}
|
||||
|
||||
public function testAddMethod() {
|
||||
public function testAddMethod(): void {
|
||||
$method = new Stmt\ClassMethod('doSomething');
|
||||
$contract = $this->createInterfaceBuilder()->addStmt($method)->getNode();
|
||||
$this->assertSame([$method], $contract->stmts);
|
||||
}
|
||||
|
||||
public function testAddConst() {
|
||||
public function testAddConst(): void {
|
||||
$const = new Stmt\ClassConst([
|
||||
new Node\Const_('SPEED_OF_LIGHT', new Float_(299792458.0))
|
||||
]);
|
||||
@@ -56,7 +56,7 @@ class InterfaceTest extends \PHPUnit\Framework\TestCase {
|
||||
$this->assertSame(299792458.0, $contract->stmts[0]->consts[0]->value->value);
|
||||
}
|
||||
|
||||
public function testOrder() {
|
||||
public function testOrder(): void {
|
||||
$const = new Stmt\ClassConst([
|
||||
new Node\Const_('SPEED_OF_LIGHT', new Float_(299792458))
|
||||
]);
|
||||
@@ -71,7 +71,7 @@ class InterfaceTest extends \PHPUnit\Framework\TestCase {
|
||||
$this->assertInstanceOf(Stmt\ClassMethod::class, $contract->stmts[1]);
|
||||
}
|
||||
|
||||
public function testDocComment() {
|
||||
public function testDocComment(): void {
|
||||
$node = $this->createInterfaceBuilder()
|
||||
->setDocComment('/** Test */')
|
||||
->getNode();
|
||||
@@ -81,7 +81,7 @@ class InterfaceTest extends \PHPUnit\Framework\TestCase {
|
||||
]), $node);
|
||||
}
|
||||
|
||||
public function testAddAttribute() {
|
||||
public function testAddAttribute(): void {
|
||||
$attribute = new Attribute(
|
||||
new Name('Attr'),
|
||||
[new Arg(new Int_(1), false, false, [], new Identifier('name'))]
|
||||
@@ -97,13 +97,13 @@ class InterfaceTest extends \PHPUnit\Framework\TestCase {
|
||||
], []), $node);
|
||||
}
|
||||
|
||||
public function testInvalidStmtError() {
|
||||
public function testInvalidStmtError(): void {
|
||||
$this->expectException(\LogicException::class);
|
||||
$this->expectExceptionMessage('Unexpected node of type "PropertyItem"');
|
||||
$this->createInterfaceBuilder()->addStmt(new Node\PropertyItem('invalid'));
|
||||
}
|
||||
|
||||
public function testFullFunctional() {
|
||||
public function testFullFunctional(): void {
|
||||
$const = new Stmt\ClassConst([
|
||||
new Node\Const_('SPEED_OF_LIGHT', new Float_(299792458))
|
||||
]);
|
||||
|
@@ -21,7 +21,7 @@ class MethodTest extends \PHPUnit\Framework\TestCase {
|
||||
return new Method($name);
|
||||
}
|
||||
|
||||
public function testModifiers() {
|
||||
public function testModifiers(): void {
|
||||
$node = $this->createMethodBuilder('test')
|
||||
->makePublic()
|
||||
->makeAbstract()
|
||||
@@ -63,7 +63,7 @@ class MethodTest extends \PHPUnit\Framework\TestCase {
|
||||
);
|
||||
}
|
||||
|
||||
public function testReturnByRef() {
|
||||
public function testReturnByRef(): void {
|
||||
$node = $this->createMethodBuilder('test')
|
||||
->makeReturnByRef()
|
||||
->getNode()
|
||||
@@ -77,7 +77,7 @@ class MethodTest extends \PHPUnit\Framework\TestCase {
|
||||
);
|
||||
}
|
||||
|
||||
public function testParams() {
|
||||
public function testParams(): void {
|
||||
$param1 = new Node\Param(new Variable('test1'));
|
||||
$param2 = new Node\Param(new Variable('test2'));
|
||||
$param3 = new Node\Param(new Variable('test3'));
|
||||
@@ -96,7 +96,7 @@ class MethodTest extends \PHPUnit\Framework\TestCase {
|
||||
);
|
||||
}
|
||||
|
||||
public function testStmts() {
|
||||
public function testStmts(): void {
|
||||
$stmt1 = new Print_(new String_('test1'));
|
||||
$stmt2 = new Print_(new String_('test2'));
|
||||
$stmt3 = new Print_(new String_('test3'));
|
||||
@@ -118,7 +118,7 @@ class MethodTest extends \PHPUnit\Framework\TestCase {
|
||||
$node
|
||||
);
|
||||
}
|
||||
public function testDocComment() {
|
||||
public function testDocComment(): void {
|
||||
$node = $this->createMethodBuilder('test')
|
||||
->setDocComment('/** Test */')
|
||||
->getNode();
|
||||
@@ -128,7 +128,7 @@ class MethodTest extends \PHPUnit\Framework\TestCase {
|
||||
]), $node);
|
||||
}
|
||||
|
||||
public function testAddAttribute() {
|
||||
public function testAddAttribute(): void {
|
||||
$attribute = new Attribute(
|
||||
new Name('Attr'),
|
||||
[new Arg(new Int_(1), false, false, [], new Identifier('name'))]
|
||||
@@ -144,7 +144,7 @@ class MethodTest extends \PHPUnit\Framework\TestCase {
|
||||
], []), $node);
|
||||
}
|
||||
|
||||
public function testReturnType() {
|
||||
public function testReturnType(): void {
|
||||
$node = $this->createMethodBuilder('test')
|
||||
->setReturnType('bool')
|
||||
->getNode();
|
||||
@@ -153,7 +153,7 @@ class MethodTest extends \PHPUnit\Framework\TestCase {
|
||||
], []), $node);
|
||||
}
|
||||
|
||||
public function testAddStmtToAbstractMethodError() {
|
||||
public function testAddStmtToAbstractMethodError(): void {
|
||||
$this->expectException(\LogicException::class);
|
||||
$this->expectExceptionMessage('Cannot add statements to an abstract method');
|
||||
$this->createMethodBuilder('test')
|
||||
@@ -162,7 +162,7 @@ class MethodTest extends \PHPUnit\Framework\TestCase {
|
||||
;
|
||||
}
|
||||
|
||||
public function testMakeMethodWithStmtsAbstractError() {
|
||||
public function testMakeMethodWithStmtsAbstractError(): void {
|
||||
$this->expectException(\LogicException::class);
|
||||
$this->expectExceptionMessage('Cannot make method with statements abstract');
|
||||
$this->createMethodBuilder('test')
|
||||
@@ -171,7 +171,7 @@ class MethodTest extends \PHPUnit\Framework\TestCase {
|
||||
;
|
||||
}
|
||||
|
||||
public function testInvalidParamError() {
|
||||
public function testInvalidParamError(): void {
|
||||
$this->expectException(\LogicException::class);
|
||||
$this->expectExceptionMessage('Expected parameter node, got "Name"');
|
||||
$this->createMethodBuilder('test')
|
||||
|
@@ -11,7 +11,7 @@ class NamespaceTest extends \PHPUnit\Framework\TestCase {
|
||||
return new Namespace_($fqn);
|
||||
}
|
||||
|
||||
public function testCreation() {
|
||||
public function testCreation(): void {
|
||||
$stmt1 = new Stmt\Class_('SomeClass');
|
||||
$stmt2 = new Stmt\Interface_('SomeInterface');
|
||||
$stmt3 = new Stmt\Function_('someFunction');
|
||||
|
@@ -21,7 +21,7 @@ class ParamTest extends \PHPUnit\Framework\TestCase {
|
||||
/**
|
||||
* @dataProvider provideTestDefaultValues
|
||||
*/
|
||||
public function testDefaultValues($value, $expectedValueNode) {
|
||||
public function testDefaultValues($value, $expectedValueNode): void {
|
||||
$node = $this->createParamBuilder('test')
|
||||
->setDefault($value)
|
||||
->getNode()
|
||||
@@ -30,7 +30,7 @@ class ParamTest extends \PHPUnit\Framework\TestCase {
|
||||
$this->assertEquals($expectedValueNode, $node->default);
|
||||
}
|
||||
|
||||
public function provideTestDefaultValues() {
|
||||
public static function provideTestDefaultValues() {
|
||||
return [
|
||||
[
|
||||
null,
|
||||
@@ -89,7 +89,7 @@ class ParamTest extends \PHPUnit\Framework\TestCase {
|
||||
* @dataProvider provideTestNullableTypes
|
||||
* @dataProvider provideTestUnionTypes
|
||||
*/
|
||||
public function testTypes($typeHint, $expectedType) {
|
||||
public function testTypes($typeHint, $expectedType): void {
|
||||
$node = $this->createParamBuilder('test')
|
||||
->setType($typeHint)
|
||||
->getNode()
|
||||
@@ -107,7 +107,7 @@ class ParamTest extends \PHPUnit\Framework\TestCase {
|
||||
$this->assertEquals($expectedType, $type);
|
||||
}
|
||||
|
||||
public function provideTestTypes() {
|
||||
public static function provideTestTypes() {
|
||||
return [
|
||||
['array', new Node\Identifier('array')],
|
||||
['callable', new Node\Identifier('callable')],
|
||||
@@ -127,7 +127,7 @@ class ParamTest extends \PHPUnit\Framework\TestCase {
|
||||
];
|
||||
}
|
||||
|
||||
public function provideTestNullableTypes() {
|
||||
public static function provideTestNullableTypes() {
|
||||
return [
|
||||
['?array', new Node\NullableType(new Node\Identifier('array'))],
|
||||
['?Some\Class', new Node\NullableType(new Node\Name('Some\Class'))],
|
||||
@@ -142,7 +142,7 @@ class ParamTest extends \PHPUnit\Framework\TestCase {
|
||||
];
|
||||
}
|
||||
|
||||
public function provideTestUnionTypes() {
|
||||
public static function provideTestUnionTypes() {
|
||||
return [
|
||||
[
|
||||
new Node\UnionType([
|
||||
@@ -169,19 +169,19 @@ class ParamTest extends \PHPUnit\Framework\TestCase {
|
||||
];
|
||||
}
|
||||
|
||||
public function testVoidTypeError() {
|
||||
public function testVoidTypeError(): void {
|
||||
$this->expectException(\LogicException::class);
|
||||
$this->expectExceptionMessage('Parameter type cannot be void');
|
||||
$this->createParamBuilder('test')->setType('void');
|
||||
}
|
||||
|
||||
public function testInvalidTypeError() {
|
||||
public function testInvalidTypeError(): void {
|
||||
$this->expectException(\LogicException::class);
|
||||
$this->expectExceptionMessage('Type must be a string, or an instance of Name, Identifier or ComplexType');
|
||||
$this->createParamBuilder('test')->setType(new \stdClass());
|
||||
}
|
||||
|
||||
public function testByRef() {
|
||||
public function testByRef(): void {
|
||||
$node = $this->createParamBuilder('test')
|
||||
->makeByRef()
|
||||
->getNode()
|
||||
@@ -193,7 +193,7 @@ class ParamTest extends \PHPUnit\Framework\TestCase {
|
||||
);
|
||||
}
|
||||
|
||||
public function testVariadic() {
|
||||
public function testVariadic(): void {
|
||||
$node = $this->createParamBuilder('test')
|
||||
->makeVariadic()
|
||||
->getNode()
|
||||
@@ -205,7 +205,7 @@ class ParamTest extends \PHPUnit\Framework\TestCase {
|
||||
);
|
||||
}
|
||||
|
||||
public function testMakePublic() {
|
||||
public function testMakePublic(): void {
|
||||
$node = $this->createParamBuilder('test')
|
||||
->makePublic()
|
||||
->getNode()
|
||||
@@ -217,7 +217,7 @@ class ParamTest extends \PHPUnit\Framework\TestCase {
|
||||
);
|
||||
}
|
||||
|
||||
public function testMakeProtected() {
|
||||
public function testMakeProtected(): void {
|
||||
$node = $this->createParamBuilder('test')
|
||||
->makeProtected()
|
||||
->getNode()
|
||||
@@ -227,9 +227,19 @@ class ParamTest extends \PHPUnit\Framework\TestCase {
|
||||
new Node\Param(new Expr\Variable('test'), null, null, false, false, [], Modifiers::PROTECTED),
|
||||
$node
|
||||
);
|
||||
|
||||
$node = $this->createParamBuilder('test')
|
||||
->makeProtectedSet()
|
||||
->getNode()
|
||||
;
|
||||
|
||||
$this->assertEquals(
|
||||
new Node\Param(new Expr\Variable('test'), null, null, false, false, [], Modifiers::PROTECTED_SET),
|
||||
$node
|
||||
);
|
||||
}
|
||||
|
||||
public function testMakePrivate() {
|
||||
public function testMakePrivate(): void {
|
||||
$node = $this->createParamBuilder('test')
|
||||
->makePrivate()
|
||||
->getNode()
|
||||
@@ -239,9 +249,19 @@ class ParamTest extends \PHPUnit\Framework\TestCase {
|
||||
new Node\Param(new Expr\Variable('test'), null, null, false, false, [], Modifiers::PRIVATE),
|
||||
$node
|
||||
);
|
||||
|
||||
$node = $this->createParamBuilder('test')
|
||||
->makePrivateSet()
|
||||
->getNode()
|
||||
;
|
||||
|
||||
$this->assertEquals(
|
||||
new Node\Param(new Expr\Variable('test'), null, null, false, false, [], Modifiers::PRIVATE_SET),
|
||||
$node
|
||||
);
|
||||
}
|
||||
|
||||
public function testMakeReadonly() {
|
||||
public function testMakeReadonly(): void {
|
||||
$node = $this->createParamBuilder('test')
|
||||
->makeReadonly()
|
||||
->getNode()
|
||||
@@ -253,7 +273,7 @@ class ParamTest extends \PHPUnit\Framework\TestCase {
|
||||
);
|
||||
}
|
||||
|
||||
public function testAddAttribute() {
|
||||
public function testAddAttribute(): void {
|
||||
$attribute = new Attribute(
|
||||
new Name('Attr'),
|
||||
[new Arg(new Int_(1), false, false, [], new Identifier('name'))]
|
||||
|
@@ -3,6 +3,7 @@
|
||||
namespace PhpParser\Builder;
|
||||
|
||||
use PhpParser\Comment;
|
||||
use PhpParser\Error;
|
||||
use PhpParser\Modifiers;
|
||||
use PhpParser\Node\Arg;
|
||||
use PhpParser\Node\Attribute;
|
||||
@@ -10,6 +11,8 @@ use PhpParser\Node\AttributeGroup;
|
||||
use PhpParser\Node\Expr;
|
||||
use PhpParser\Node\Identifier;
|
||||
use PhpParser\Node\Name;
|
||||
use PhpParser\Node\PropertyHook;
|
||||
use PhpParser\Node\PropertyItem;
|
||||
use PhpParser\Node\Scalar;
|
||||
use PhpParser\Node\Scalar\Int_;
|
||||
use PhpParser\Node\Stmt;
|
||||
@@ -19,7 +22,7 @@ class PropertyTest extends \PHPUnit\Framework\TestCase {
|
||||
return new Property($name);
|
||||
}
|
||||
|
||||
public function testModifiers() {
|
||||
public function testModifiers(): void {
|
||||
$node = $this->createPropertyBuilder('test')
|
||||
->makePrivate()
|
||||
->makeStatic()
|
||||
@@ -29,9 +32,7 @@ class PropertyTest extends \PHPUnit\Framework\TestCase {
|
||||
$this->assertEquals(
|
||||
new Stmt\Property(
|
||||
Modifiers::PRIVATE | Modifiers::STATIC,
|
||||
[
|
||||
new \PhpParser\Node\PropertyItem('test')
|
||||
]
|
||||
[new PropertyItem('test')]
|
||||
),
|
||||
$node
|
||||
);
|
||||
@@ -44,9 +45,7 @@ class PropertyTest extends \PHPUnit\Framework\TestCase {
|
||||
$this->assertEquals(
|
||||
new Stmt\Property(
|
||||
Modifiers::PROTECTED,
|
||||
[
|
||||
new \PhpParser\Node\PropertyItem('test')
|
||||
]
|
||||
[new PropertyItem('test')]
|
||||
),
|
||||
$node
|
||||
);
|
||||
@@ -59,9 +58,7 @@ class PropertyTest extends \PHPUnit\Framework\TestCase {
|
||||
$this->assertEquals(
|
||||
new Stmt\Property(
|
||||
Modifiers::PUBLIC,
|
||||
[
|
||||
new \PhpParser\Node\PropertyItem('test')
|
||||
]
|
||||
[new PropertyItem('test')]
|
||||
),
|
||||
$node
|
||||
);
|
||||
@@ -74,15 +71,40 @@ class PropertyTest extends \PHPUnit\Framework\TestCase {
|
||||
$this->assertEquals(
|
||||
new Stmt\Property(
|
||||
Modifiers::READONLY,
|
||||
[
|
||||
new \PhpParser\Node\PropertyItem('test')
|
||||
]
|
||||
[new PropertyItem('test')]
|
||||
),
|
||||
$node
|
||||
);
|
||||
|
||||
$node = $this->createPropertyBuilder('test')
|
||||
->makeFinal()
|
||||
->getNode();
|
||||
$this->assertEquals(
|
||||
new Stmt\Property(Modifiers::FINAL, [new PropertyItem('test')]),
|
||||
$node);
|
||||
|
||||
$node = $this->createPropertyBuilder('test')
|
||||
->makePrivateSet()
|
||||
->getNode();
|
||||
$this->assertEquals(
|
||||
new Stmt\Property(Modifiers::PRIVATE_SET, [new PropertyItem('test')]),
|
||||
$node);
|
||||
|
||||
$node = $this->createPropertyBuilder('test')
|
||||
->makeProtectedSet()
|
||||
->getNode();
|
||||
$this->assertEquals(
|
||||
new Stmt\Property(Modifiers::PROTECTED_SET, [new PropertyItem('test')]),
|
||||
$node);
|
||||
}
|
||||
|
||||
public function testDocComment() {
|
||||
public function testAbstractWithoutHook() {
|
||||
$this->expectException(Error::class);
|
||||
$this->expectExceptionMessage('Only hooked properties may be declared abstract');
|
||||
$this->createPropertyBuilder('test')->makeAbstract()->getNode();
|
||||
}
|
||||
|
||||
public function testDocComment(): void {
|
||||
$node = $this->createPropertyBuilder('test')
|
||||
->setDocComment('/** Test */')
|
||||
->getNode();
|
||||
@@ -101,7 +123,7 @@ class PropertyTest extends \PHPUnit\Framework\TestCase {
|
||||
/**
|
||||
* @dataProvider provideTestDefaultValues
|
||||
*/
|
||||
public function testDefaultValues($value, $expectedValueNode) {
|
||||
public function testDefaultValues($value, $expectedValueNode): void {
|
||||
$node = $this->createPropertyBuilder('test')
|
||||
->setDefault($value)
|
||||
->getNode()
|
||||
@@ -110,7 +132,7 @@ class PropertyTest extends \PHPUnit\Framework\TestCase {
|
||||
$this->assertEquals($expectedValueNode, $node->props[0]->default);
|
||||
}
|
||||
|
||||
public function testAddAttribute() {
|
||||
public function testAddAttribute(): void {
|
||||
$attribute = new Attribute(
|
||||
new Name('Attr'),
|
||||
[new Arg(new Int_(1), false, false, [], new Identifier('name'))]
|
||||
@@ -136,7 +158,24 @@ class PropertyTest extends \PHPUnit\Framework\TestCase {
|
||||
);
|
||||
}
|
||||
|
||||
public function provideTestDefaultValues() {
|
||||
public function testAddHook(): void {
|
||||
$get = new PropertyHook('get', null);
|
||||
$set = new PropertyHook('set', null);
|
||||
$node = $this->createPropertyBuilder('test')
|
||||
->addHook($get)
|
||||
->addHook($set)
|
||||
->makeAbstract()
|
||||
->getNode();
|
||||
$this->assertEquals(
|
||||
new Stmt\Property(
|
||||
Modifiers::ABSTRACT,
|
||||
[new PropertyItem('test')],
|
||||
[], null, [],
|
||||
[$get, $set]),
|
||||
$node);
|
||||
}
|
||||
|
||||
public static function provideTestDefaultValues() {
|
||||
return [
|
||||
[
|
||||
null,
|
||||
|
@@ -23,7 +23,7 @@ class TraitTest extends \PHPUnit\Framework\TestCase {
|
||||
return new Trait_($class);
|
||||
}
|
||||
|
||||
public function testStmtAddition() {
|
||||
public function testStmtAddition(): void {
|
||||
$method1 = new Stmt\ClassMethod('test1');
|
||||
$method2 = new Stmt\ClassMethod('test2');
|
||||
$method3 = new Stmt\ClassMethod('test3');
|
||||
@@ -49,7 +49,7 @@ class TraitTest extends \PHPUnit\Framework\TestCase {
|
||||
]), $trait);
|
||||
}
|
||||
|
||||
public function testInvalidStmtError() {
|
||||
public function testInvalidStmtError(): void {
|
||||
$this->expectException(\LogicException::class);
|
||||
$this->expectExceptionMessage('Unexpected node of type "Stmt_Echo"');
|
||||
$this->createTraitBuilder('Test')
|
||||
@@ -57,7 +57,7 @@ class TraitTest extends \PHPUnit\Framework\TestCase {
|
||||
;
|
||||
}
|
||||
|
||||
public function testGetMethods() {
|
||||
public function testGetMethods(): void {
|
||||
$methods = [
|
||||
new ClassMethod('foo'),
|
||||
new ClassMethod('bar'),
|
||||
@@ -77,7 +77,7 @@ class TraitTest extends \PHPUnit\Framework\TestCase {
|
||||
$this->assertSame($methods, $trait->getMethods());
|
||||
}
|
||||
|
||||
public function testGetProperties() {
|
||||
public function testGetProperties(): void {
|
||||
$properties = [
|
||||
new Property(Modifiers::PUBLIC, [new PropertyItem('foo')]),
|
||||
new Property(Modifiers::PUBLIC, [new PropertyItem('bar')]),
|
||||
@@ -95,7 +95,7 @@ class TraitTest extends \PHPUnit\Framework\TestCase {
|
||||
$this->assertSame($properties, $trait->getProperties());
|
||||
}
|
||||
|
||||
public function testAddAttribute() {
|
||||
public function testAddAttribute(): void {
|
||||
$attribute = new Attribute(
|
||||
new Name('Attr'),
|
||||
[new Arg(new Int_(1), false, false, [], new Identifier('name'))]
|
||||
|
@@ -12,7 +12,7 @@ class TraitUseAdaptationTest extends \PHPUnit\Framework\TestCase {
|
||||
return new TraitUseAdaptation($trait, $method);
|
||||
}
|
||||
|
||||
public function testAsMake() {
|
||||
public function testAsMake(): void {
|
||||
$builder = $this->createTraitUseAdaptationBuilder(null, 'foo');
|
||||
|
||||
$this->assertEquals(
|
||||
@@ -36,7 +36,7 @@ class TraitUseAdaptationTest extends \PHPUnit\Framework\TestCase {
|
||||
);
|
||||
}
|
||||
|
||||
public function testInsteadof() {
|
||||
public function testInsteadof(): void {
|
||||
$node = $this->createTraitUseAdaptationBuilder('SomeTrait', 'foo')
|
||||
->insteadof('AnotherTrait')
|
||||
->getNode()
|
||||
@@ -52,7 +52,7 @@ class TraitUseAdaptationTest extends \PHPUnit\Framework\TestCase {
|
||||
);
|
||||
}
|
||||
|
||||
public function testAsOnNotAlias() {
|
||||
public function testAsOnNotAlias(): void {
|
||||
$this->expectException(\LogicException::class);
|
||||
$this->expectExceptionMessage('Cannot set alias for not alias adaptation buider');
|
||||
$this->createTraitUseAdaptationBuilder('Test', 'foo')
|
||||
@@ -61,7 +61,7 @@ class TraitUseAdaptationTest extends \PHPUnit\Framework\TestCase {
|
||||
;
|
||||
}
|
||||
|
||||
public function testInsteadofOnNotPrecedence() {
|
||||
public function testInsteadofOnNotPrecedence(): void {
|
||||
$this->expectException(\LogicException::class);
|
||||
$this->expectExceptionMessage('Cannot add overwritten traits for not precedence adaptation buider');
|
||||
$this->createTraitUseAdaptationBuilder('Test', 'foo')
|
||||
@@ -70,7 +70,7 @@ class TraitUseAdaptationTest extends \PHPUnit\Framework\TestCase {
|
||||
;
|
||||
}
|
||||
|
||||
public function testInsteadofWithoutTrait() {
|
||||
public function testInsteadofWithoutTrait(): void {
|
||||
$this->expectException(\LogicException::class);
|
||||
$this->expectExceptionMessage('Precedence adaptation must have trait');
|
||||
$this->createTraitUseAdaptationBuilder(null, 'foo')
|
||||
@@ -78,7 +78,7 @@ class TraitUseAdaptationTest extends \PHPUnit\Framework\TestCase {
|
||||
;
|
||||
}
|
||||
|
||||
public function testMakeOnNotAlias() {
|
||||
public function testMakeOnNotAlias(): void {
|
||||
$this->expectException(\LogicException::class);
|
||||
$this->expectExceptionMessage('Cannot set access modifier for not alias adaptation buider');
|
||||
$this->createTraitUseAdaptationBuilder('Test', 'foo')
|
||||
@@ -87,7 +87,7 @@ class TraitUseAdaptationTest extends \PHPUnit\Framework\TestCase {
|
||||
;
|
||||
}
|
||||
|
||||
public function testMultipleMake() {
|
||||
public function testMultipleMake(): void {
|
||||
$this->expectException(\LogicException::class);
|
||||
$this->expectExceptionMessage('Multiple access type modifiers are not allowed');
|
||||
$this->createTraitUseAdaptationBuilder(null, 'foo')
|
||||
@@ -96,7 +96,7 @@ class TraitUseAdaptationTest extends \PHPUnit\Framework\TestCase {
|
||||
;
|
||||
}
|
||||
|
||||
public function testUndefinedType() {
|
||||
public function testUndefinedType(): void {
|
||||
$this->expectException(\LogicException::class);
|
||||
$this->expectExceptionMessage('Type of adaptation is not defined');
|
||||
$this->createTraitUseAdaptationBuilder(null, 'foo')
|
||||
|
@@ -10,7 +10,7 @@ class TraitUseTest extends \PHPUnit\Framework\TestCase {
|
||||
return new TraitUse(...$traits);
|
||||
}
|
||||
|
||||
public function testAnd() {
|
||||
public function testAnd(): void {
|
||||
$node = $this->createTraitUseBuilder('SomeTrait')
|
||||
->and('AnotherTrait')
|
||||
->getNode()
|
||||
@@ -25,7 +25,7 @@ class TraitUseTest extends \PHPUnit\Framework\TestCase {
|
||||
);
|
||||
}
|
||||
|
||||
public function testWith() {
|
||||
public function testWith(): void {
|
||||
$node = $this->createTraitUseBuilder('SomeTrait')
|
||||
->with(new Stmt\TraitUseAdaptation\Alias(null, 'foo', null, 'bar'))
|
||||
->with((new TraitUseAdaptation(null, 'test'))->as('baz'))
|
||||
@@ -41,7 +41,7 @@ class TraitUseTest extends \PHPUnit\Framework\TestCase {
|
||||
);
|
||||
}
|
||||
|
||||
public function testInvalidAdaptationNode() {
|
||||
public function testInvalidAdaptationNode(): void {
|
||||
$this->expectException(\LogicException::class);
|
||||
$this->expectExceptionMessage('Adaptation must have type TraitUseAdaptation');
|
||||
$this->createTraitUseBuilder('Test')
|
||||
|
@@ -11,7 +11,7 @@ class UseTest extends \PHPUnit\Framework\TestCase {
|
||||
return new Builder\Use_($name, $type);
|
||||
}
|
||||
|
||||
public function testCreation() {
|
||||
public function testCreation(): void {
|
||||
$node = $this->createUseBuilder('Foo\Bar')->getNode();
|
||||
$this->assertEquals(new Stmt\Use_([
|
||||
new \PhpParser\Node\UseItem(new Name('Foo\Bar'), null)
|
||||
|
@@ -15,12 +15,12 @@ class BuilderFactoryTest extends \PHPUnit\Framework\TestCase {
|
||||
/**
|
||||
* @dataProvider provideTestFactory
|
||||
*/
|
||||
public function testFactory($methodName, $className) {
|
||||
public function testFactory($methodName, $className): void {
|
||||
$factory = new BuilderFactory();
|
||||
$this->assertInstanceOf($className, $factory->$methodName('test'));
|
||||
}
|
||||
|
||||
public function provideTestFactory() {
|
||||
public static function provideTestFactory() {
|
||||
return [
|
||||
['namespace', Builder\Namespace_::class],
|
||||
['class', Builder\Class_::class],
|
||||
@@ -38,12 +38,12 @@ class BuilderFactoryTest extends \PHPUnit\Framework\TestCase {
|
||||
];
|
||||
}
|
||||
|
||||
public function testFactoryClassConst() {
|
||||
public function testFactoryClassConst(): void {
|
||||
$factory = new BuilderFactory();
|
||||
$this->assertInstanceOf(Builder\ClassConst::class, $factory->classConst('TEST', 1));
|
||||
}
|
||||
|
||||
public function testAttribute() {
|
||||
public function testAttribute(): void {
|
||||
$factory = new BuilderFactory();
|
||||
$this->assertEquals(
|
||||
new Attribute(new Name('AttributeName'), [new Arg(
|
||||
@@ -53,7 +53,7 @@ class BuilderFactoryTest extends \PHPUnit\Framework\TestCase {
|
||||
);
|
||||
}
|
||||
|
||||
public function testVal() {
|
||||
public function testVal(): void {
|
||||
// This method is a wrapper around BuilderHelpers::normalizeValue(),
|
||||
// which is already tested elsewhere
|
||||
$factory = new BuilderFactory();
|
||||
@@ -63,7 +63,7 @@ class BuilderFactoryTest extends \PHPUnit\Framework\TestCase {
|
||||
);
|
||||
}
|
||||
|
||||
public function testConcat() {
|
||||
public function testConcat(): void {
|
||||
$factory = new BuilderFactory();
|
||||
$varA = new Expr\Variable('a');
|
||||
$varB = new Expr\Variable('b');
|
||||
@@ -83,19 +83,19 @@ class BuilderFactoryTest extends \PHPUnit\Framework\TestCase {
|
||||
);
|
||||
}
|
||||
|
||||
public function testConcatOneError() {
|
||||
public function testConcatOneError(): void {
|
||||
$this->expectException(\LogicException::class);
|
||||
$this->expectExceptionMessage('Expected at least two expressions');
|
||||
(new BuilderFactory())->concat("a");
|
||||
}
|
||||
|
||||
public function testConcatInvalidExpr() {
|
||||
public function testConcatInvalidExpr(): void {
|
||||
$this->expectException(\LogicException::class);
|
||||
$this->expectExceptionMessage('Expected string or Expr');
|
||||
(new BuilderFactory())->concat("a", 42);
|
||||
}
|
||||
|
||||
public function testArgs() {
|
||||
public function testArgs(): void {
|
||||
$factory = new BuilderFactory();
|
||||
$unpack = new Arg(new Expr\Variable('c'), false, true);
|
||||
$this->assertEquals(
|
||||
@@ -108,7 +108,7 @@ class BuilderFactoryTest extends \PHPUnit\Framework\TestCase {
|
||||
);
|
||||
}
|
||||
|
||||
public function testNamedArgs() {
|
||||
public function testNamedArgs(): void {
|
||||
$factory = new BuilderFactory();
|
||||
$this->assertEquals(
|
||||
[
|
||||
@@ -119,7 +119,7 @@ class BuilderFactoryTest extends \PHPUnit\Framework\TestCase {
|
||||
);
|
||||
}
|
||||
|
||||
public function testCalls() {
|
||||
public function testCalls(): void {
|
||||
$factory = new BuilderFactory();
|
||||
|
||||
// Simple function call
|
||||
@@ -195,7 +195,7 @@ class BuilderFactoryTest extends \PHPUnit\Framework\TestCase {
|
||||
);
|
||||
}
|
||||
|
||||
public function testConstFetches() {
|
||||
public function testConstFetches(): void {
|
||||
$factory = new BuilderFactory();
|
||||
$this->assertEquals(
|
||||
new Expr\ConstFetch(new Name('FOO')),
|
||||
@@ -215,7 +215,7 @@ class BuilderFactoryTest extends \PHPUnit\Framework\TestCase {
|
||||
);
|
||||
}
|
||||
|
||||
public function testVar() {
|
||||
public function testVar(): void {
|
||||
$factory = new BuilderFactory();
|
||||
$this->assertEquals(
|
||||
new Expr\Variable("foo"),
|
||||
@@ -227,7 +227,7 @@ class BuilderFactoryTest extends \PHPUnit\Framework\TestCase {
|
||||
);
|
||||
}
|
||||
|
||||
public function testPropertyFetch() {
|
||||
public function testPropertyFetch(): void {
|
||||
$f = new BuilderFactory();
|
||||
$this->assertEquals(
|
||||
new Expr\PropertyFetch(new Expr\Variable('foo'), 'bar'),
|
||||
@@ -243,31 +243,31 @@ class BuilderFactoryTest extends \PHPUnit\Framework\TestCase {
|
||||
);
|
||||
}
|
||||
|
||||
public function testInvalidIdentifier() {
|
||||
public function testInvalidIdentifier(): void {
|
||||
$this->expectException(\LogicException::class);
|
||||
$this->expectExceptionMessage('Expected string or instance of Node\Identifier');
|
||||
(new BuilderFactory())->classConstFetch('Foo', new Name('foo'));
|
||||
}
|
||||
|
||||
public function testInvalidIdentifierOrExpr() {
|
||||
public function testInvalidIdentifierOrExpr(): void {
|
||||
$this->expectException(\LogicException::class);
|
||||
$this->expectExceptionMessage('Expected string or instance of Node\Identifier or Node\Expr');
|
||||
(new BuilderFactory())->staticCall('Foo', new Name('bar'));
|
||||
}
|
||||
|
||||
public function testInvalidNameOrExpr() {
|
||||
public function testInvalidNameOrExpr(): void {
|
||||
$this->expectException(\LogicException::class);
|
||||
$this->expectExceptionMessage('Name must be a string or an instance of Node\Name or Node\Expr');
|
||||
(new BuilderFactory())->funcCall(new Node\Stmt\Return_());
|
||||
}
|
||||
|
||||
public function testInvalidVar() {
|
||||
public function testInvalidVar(): void {
|
||||
$this->expectException(\LogicException::class);
|
||||
$this->expectExceptionMessage('Variable name must be string or Expr');
|
||||
(new BuilderFactory())->var(new Node\Stmt\Return_());
|
||||
}
|
||||
|
||||
public function testIntegration() {
|
||||
public function testIntegration(): void {
|
||||
$factory = new BuilderFactory();
|
||||
$node = $factory->namespace('Name\Space')
|
||||
->addStmt($factory->use('Foo\Bar\SomeOtherClass'))
|
||||
@@ -365,7 +365,10 @@ abstract class SomeClass extends SomeOtherClass implements A\Few, \Interfaces
|
||||
* @param SomeClass And takes a parameter
|
||||
*/
|
||||
abstract public function someMethod(SomeClass $someParam);
|
||||
protected function anotherMethod(#[TaggedIterator('app.handlers')] $someParam = 'test')
|
||||
protected function anotherMethod(
|
||||
#[TaggedIterator('app.handlers')]
|
||||
$someParam = 'test'
|
||||
)
|
||||
{
|
||||
print $someParam;
|
||||
}
|
||||
|
@@ -4,12 +4,13 @@ namespace PhpParser;
|
||||
|
||||
use PhpParser\Builder\Class_;
|
||||
use PhpParser\Node\Identifier;
|
||||
use PhpParser\Node\Name\FullyQualified;
|
||||
use PhpParser\Node\Scalar;
|
||||
use PhpParser\Node\Stmt;
|
||||
use PhpParser\Node\Expr;
|
||||
|
||||
class BuilderHelpersTest extends \PHPUnit\Framework\TestCase {
|
||||
public function testNormalizeNode() {
|
||||
public function testNormalizeNode(): void {
|
||||
$builder = new Class_('SomeClass');
|
||||
$this->assertEquals($builder->getNode(), BuilderHelpers::normalizeNode($builder));
|
||||
|
||||
@@ -21,7 +22,7 @@ class BuilderHelpersTest extends \PHPUnit\Framework\TestCase {
|
||||
BuilderHelpers::normalizeNode('test');
|
||||
}
|
||||
|
||||
public function testNormalizeStmt() {
|
||||
public function testNormalizeStmt(): void {
|
||||
$stmt = new Node\Stmt\Class_('Class');
|
||||
$this->assertSame($stmt, BuilderHelpers::normalizeStmt($stmt));
|
||||
|
||||
@@ -35,13 +36,13 @@ class BuilderHelpersTest extends \PHPUnit\Framework\TestCase {
|
||||
BuilderHelpers::normalizeStmt(new Node\Attribute(new Node\Name('Test')));
|
||||
}
|
||||
|
||||
public function testNormalizeStmtInvalidType() {
|
||||
public function testNormalizeStmtInvalidType(): void {
|
||||
$this->expectException(\LogicException::class);
|
||||
$this->expectExceptionMessage('Expected node or builder object');
|
||||
BuilderHelpers::normalizeStmt('test');
|
||||
}
|
||||
|
||||
public function testNormalizeIdentifier() {
|
||||
public function testNormalizeIdentifier(): void {
|
||||
$identifier = new Node\Identifier('fn');
|
||||
$this->assertSame($identifier, BuilderHelpers::normalizeIdentifier($identifier));
|
||||
$this->assertEquals($identifier, BuilderHelpers::normalizeIdentifier('fn'));
|
||||
@@ -51,7 +52,7 @@ class BuilderHelpersTest extends \PHPUnit\Framework\TestCase {
|
||||
BuilderHelpers::normalizeIdentifier(1);
|
||||
}
|
||||
|
||||
public function testNormalizeIdentifierOrExpr() {
|
||||
public function testNormalizeIdentifierOrExpr(): void {
|
||||
$identifier = new Node\Identifier('fn');
|
||||
$this->assertSame($identifier, BuilderHelpers::normalizeIdentifierOrExpr($identifier));
|
||||
|
||||
@@ -64,7 +65,7 @@ class BuilderHelpersTest extends \PHPUnit\Framework\TestCase {
|
||||
BuilderHelpers::normalizeIdentifierOrExpr(1);
|
||||
}
|
||||
|
||||
public function testNormalizeName() {
|
||||
public function testNormalizeName(): void {
|
||||
$name = new Node\Name('test');
|
||||
$this->assertSame($name, BuilderHelpers::normalizeName($name));
|
||||
$this->assertEquals(
|
||||
@@ -82,13 +83,13 @@ class BuilderHelpersTest extends \PHPUnit\Framework\TestCase {
|
||||
BuilderHelpers::normalizeName('');
|
||||
}
|
||||
|
||||
public function testNormalizeNameInvalidType() {
|
||||
public function testNormalizeNameInvalidType(): void {
|
||||
$this->expectException(\LogicException::class);
|
||||
$this->expectExceptionMessage('Name must be a string or an instance of Node\Name');
|
||||
BuilderHelpers::normalizeName(1);
|
||||
}
|
||||
|
||||
public function testNormalizeNameOrExpr() {
|
||||
public function testNormalizeNameOrExpr(): void {
|
||||
$expr = new Expr\Variable('fn');
|
||||
$this->assertSame($expr, BuilderHelpers::normalizeNameOrExpr($expr));
|
||||
|
||||
@@ -109,13 +110,13 @@ class BuilderHelpersTest extends \PHPUnit\Framework\TestCase {
|
||||
BuilderHelpers::normalizeNameOrExpr('');
|
||||
}
|
||||
|
||||
public function testNormalizeNameOrExpInvalidType() {
|
||||
public function testNormalizeNameOrExpInvalidType(): void {
|
||||
$this->expectException(\LogicException::class);
|
||||
$this->expectExceptionMessage('Name must be a string or an instance of Node\Name or Node\Expr');
|
||||
BuilderHelpers::normalizeNameOrExpr(1);
|
||||
}
|
||||
|
||||
public function testNormalizeType() {
|
||||
public function testNormalizeType(): void {
|
||||
$this->assertEquals(new Node\Identifier('array'), BuilderHelpers::normalizeType('array'));
|
||||
$this->assertEquals(new Node\Identifier('callable'), BuilderHelpers::normalizeType('callable'));
|
||||
$this->assertEquals(new Node\Identifier('string'), BuilderHelpers::normalizeType('string'));
|
||||
@@ -156,25 +157,25 @@ class BuilderHelpersTest extends \PHPUnit\Framework\TestCase {
|
||||
BuilderHelpers::normalizeType(1);
|
||||
}
|
||||
|
||||
public function testNormalizeTypeNullableVoid() {
|
||||
public function testNormalizeTypeNullableVoid(): void {
|
||||
$this->expectException(\LogicException::class);
|
||||
$this->expectExceptionMessage('void type cannot be nullable');
|
||||
BuilderHelpers::normalizeType('?void');
|
||||
}
|
||||
|
||||
public function testNormalizeTypeNullableMixed() {
|
||||
public function testNormalizeTypeNullableMixed(): void {
|
||||
$this->expectException(\LogicException::class);
|
||||
$this->expectExceptionMessage('mixed type cannot be nullable');
|
||||
BuilderHelpers::normalizeType('?mixed');
|
||||
}
|
||||
|
||||
public function testNormalizeTypeNullableNever() {
|
||||
public function testNormalizeTypeNullableNever(): void {
|
||||
$this->expectException(\LogicException::class);
|
||||
$this->expectExceptionMessage('never type cannot be nullable');
|
||||
BuilderHelpers::normalizeType('?never');
|
||||
}
|
||||
|
||||
public function testNormalizeValue() {
|
||||
public function testNormalizeValue(): void {
|
||||
$expression = new Scalar\Int_(1);
|
||||
$this->assertSame($expression, BuilderHelpers::normalizeValue($expression));
|
||||
|
||||
@@ -200,7 +201,7 @@ class BuilderHelpersTest extends \PHPUnit\Framework\TestCase {
|
||||
BuilderHelpers::normalizeValue(new \stdClass());
|
||||
}
|
||||
|
||||
public function testNormalizeDocComment() {
|
||||
public function testNormalizeDocComment(): void {
|
||||
$docComment = new Comment\Doc('Some doc comment');
|
||||
$this->assertSame($docComment, BuilderHelpers::normalizeDocComment($docComment));
|
||||
|
||||
@@ -211,7 +212,7 @@ class BuilderHelpersTest extends \PHPUnit\Framework\TestCase {
|
||||
BuilderHelpers::normalizeDocComment(1);
|
||||
}
|
||||
|
||||
public function testNormalizeAttribute() {
|
||||
public function testNormalizeAttribute(): void {
|
||||
$attribute = new Node\Attribute(new Node\Name('Test'));
|
||||
$attributeGroup = new Node\AttributeGroup([$attribute]);
|
||||
|
||||
@@ -222,4 +223,14 @@ class BuilderHelpersTest extends \PHPUnit\Framework\TestCase {
|
||||
$this->expectExceptionMessage('Attribute must be an instance of PhpParser\Node\Attribute or PhpParser\Node\AttributeGroup');
|
||||
BuilderHelpers::normalizeAttribute('test');
|
||||
}
|
||||
|
||||
public function testNormalizeValueEnum() {
|
||||
if (\PHP_VERSION_ID <= 80100) {
|
||||
$this->markTestSkipped('Enums are supported since PHP 8.1');
|
||||
}
|
||||
|
||||
include __DIR__ . '/../fixtures/Suit.php';
|
||||
|
||||
$this->assertEquals(new Expr\ClassConstFetch(new FullyQualified(\Suit::class), new Identifier('Hearts')), BuilderHelpers::normalizeValue(\Suit::Hearts));
|
||||
}
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user