mirror of
https://github.com/nikic/PHP-Parser.git
synced 2025-06-21 23:32:59 +02:00
Compare commits
88 Commits
Author | SHA1 | Date | |
---|---|---|---|
210577fe3c | |||
f09f22760e | |||
d4cb98ae38 | |||
68d2a52b42 | |||
63f8699143 | |||
4122ff3a91 | |||
6f1f206862 | |||
99a24b6a55 | |||
63a79e8daa | |||
6a21234e58 | |||
7064539974 | |||
4bfc4595ed | |||
54f19a0a66 | |||
8da6d7ac62 | |||
f6e1fbf3a2 | |||
50953a2691 | |||
0a20979a62 | |||
a45fb2a621 | |||
08501991d4 | |||
b5234eacd0 | |||
632ead3a82 | |||
13549aa794 | |||
d2c645f163 | |||
def24f2224 | |||
cfeb195205 | |||
ace6c67a8a | |||
0483391aca | |||
9aebf377fc | |||
5a43015499 | |||
6608f01670 | |||
55c4269232 | |||
c4304c76bd | |||
b099e8fc76 | |||
acf16edc8e | |||
a8b5ed4306 | |||
c758510a37 | |||
feed91cf0f | |||
fe14cf3672 | |||
3fb73520c1 | |||
c35cc4b2cb | |||
e69ebbbfd9 | |||
0b258d9a9e | |||
2d193bb0e4 | |||
49e9951f2c | |||
eccf1bf464 | |||
b67560d388 | |||
4432ba399e | |||
37ac4ea9c2 | |||
4848a0d734 | |||
8eb194ea1f | |||
e03d63cffb | |||
ce91d139b5 | |||
33d7c8d3d8 | |||
6b409b96bb | |||
f68e1a43ff | |||
f767b9fd9f | |||
2a4bb7ef2c | |||
4abdcde5f1 | |||
d46f261ef9 | |||
38aa0920c9 | |||
a8223f228a | |||
8165cf69fa | |||
46221a0914 | |||
3b87eb721c | |||
e3471d94d3 | |||
c6d052fc58 | |||
8008d07bef | |||
7284a4d019 | |||
d3d1ee470a | |||
893a5bce3f | |||
bec74aceda | |||
7c09e096c9 | |||
0f64504317 | |||
5e36ef732e | |||
dbe56d23de | |||
c64986fa55 | |||
bc7a9bf9c2 | |||
2816485126 | |||
1d1bc8a364 | |||
d520bc9e1d | |||
51e0b30843 | |||
eff72eeffa | |||
658f1be311 | |||
b9b65a2996 | |||
1b479e7592 | |||
b5351f883a | |||
88be6127fa | |||
8a97fa157f |
9
.editorconfig
Normal file
9
.editorconfig
Normal file
@ -0,0 +1,9 @@
|
||||
root = true
|
||||
|
||||
[*.y]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
indent_style = space
|
||||
indent_size = 4
|
17
.gitattributes
vendored
17
.gitattributes
vendored
@ -1,9 +1,10 @@
|
||||
/doc export-ignore
|
||||
/test export-ignore
|
||||
/test_old export-ignore
|
||||
.gitattributes export-ignore
|
||||
.gitignore export-ignore
|
||||
.travis.yml export-ignore
|
||||
CHANGELOG.md export-ignore
|
||||
/.github export-ignore
|
||||
/doc export-ignore
|
||||
/test export-ignore
|
||||
/test_old export-ignore
|
||||
.editorconfig export-ignore
|
||||
.gitattributes export-ignore
|
||||
.gitignore export-ignore
|
||||
CHANGELOG.md export-ignore
|
||||
phpunit.xml.dist export-ignore
|
||||
UPGRADE-*.md export-ignore
|
||||
UPGRADE-*.md export-ignore
|
||||
|
90
.github/workflows/main.yml
vendored
Normal file
90
.github/workflows/main.yml
vendored
Normal file
@ -0,0 +1,90 @@
|
||||
# https://help.github.com/en/categories/automating-your-workflow-with-github-actions
|
||||
name: Main
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
tests_70:
|
||||
runs-on: "ubuntu-latest"
|
||||
name: "PHP 7.0 Unit Tests"
|
||||
steps:
|
||||
- name: "Checkout"
|
||||
uses: "actions/checkout@v2"
|
||||
- name: "Install PHP"
|
||||
uses: "shivammathur/setup-php@v2"
|
||||
with:
|
||||
coverage: "xdebug"
|
||||
php-version: "7.0"
|
||||
tools: composer:v2
|
||||
- name: "Install dependencies"
|
||||
run: |
|
||||
composer require php-coveralls/php-coveralls:^2.2 --dev --no-update
|
||||
composer update --no-progress --prefer-dist
|
||||
- name: "Tests"
|
||||
run: "php vendor/bin/phpunit --coverage-clover build/logs/clover.xml"
|
||||
- name: Coveralls
|
||||
env:
|
||||
COVERALLS_REPO_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: "php vendor/bin/php-coveralls"
|
||||
if: ${{ success() }}
|
||||
tests:
|
||||
runs-on: "ubuntu-latest"
|
||||
name: "PHP ${{ matrix.php-version }} Unit Tests"
|
||||
strategy:
|
||||
matrix:
|
||||
php-version:
|
||||
- "7.1"
|
||||
- "7.2"
|
||||
- "7.3"
|
||||
- "7.4"
|
||||
- "8.0"
|
||||
- "8.1"
|
||||
include:
|
||||
- php-version: "8.1"
|
||||
flags: "--ignore-platform-req=php"
|
||||
steps:
|
||||
- name: "Checkout"
|
||||
uses: "actions/checkout@v2"
|
||||
- name: "Install PHP"
|
||||
uses: "shivammathur/setup-php@v2"
|
||||
with:
|
||||
coverage: "none"
|
||||
php-version: "${{ matrix.php-version }}"
|
||||
tools: composer:v2
|
||||
- name: "Install dependencies"
|
||||
run: "composer update --no-progress --prefer-dist ${{ matrix.flags }}"
|
||||
- name: "PHPUnit"
|
||||
run: "php vendor/bin/phpunit"
|
||||
test_old_73_80:
|
||||
runs-on: "ubuntu-latest"
|
||||
name: "PHP 7.3 Code on PHP 8.0 Integration Tests"
|
||||
steps:
|
||||
- name: "Checkout"
|
||||
uses: "actions/checkout@v2"
|
||||
- name: "Install PHP"
|
||||
uses: "shivammathur/setup-php@v2"
|
||||
with:
|
||||
coverage: "none"
|
||||
php-version: "8.0"
|
||||
tools: composer:v2
|
||||
- name: "Install PHP 8 dependencies"
|
||||
run: "composer update --no-progress --prefer-dist"
|
||||
- name: "Tests"
|
||||
run: "test_old/run-php-src.sh 7.3.21"
|
||||
test_old_80_70:
|
||||
runs-on: "ubuntu-latest"
|
||||
name: "PHP 8.0 Code on PHP 7.0 Integration Tests"
|
||||
steps:
|
||||
- name: "Checkout"
|
||||
uses: "actions/checkout@v2"
|
||||
- name: "Install PHP"
|
||||
uses: "shivammathur/setup-php@v2"
|
||||
with:
|
||||
coverage: "none"
|
||||
php-version: "7.0"
|
||||
tools: composer:v2
|
||||
- name: "Install PHP 8 dependencies"
|
||||
run: "composer update --no-progress --prefer-dist"
|
||||
- name: "Tests"
|
||||
run: "test_old/run-php-src.sh 8.0.0"
|
43
.travis.yml
43
.travis.yml
@ -1,43 +0,0 @@
|
||||
language: php
|
||||
dist: xenial
|
||||
|
||||
before_install: composer self-update --2
|
||||
|
||||
install: composer update --no-progress --prefer-dist
|
||||
|
||||
matrix:
|
||||
include:
|
||||
- name: PHP 7.0 Unit Tests
|
||||
php: 7.0
|
||||
install:
|
||||
- composer require php-coveralls/php-coveralls:^2.2 --dev --no-update
|
||||
- composer update --no-progress --prefer-dist
|
||||
script: vendor/bin/phpunit --coverage-clover build/logs/clover.xml
|
||||
after_success: php vendor/bin/coveralls
|
||||
- name: PHP 7.1 Unit Tests
|
||||
php: 7.1
|
||||
- name: PHP 7.2 Unit Tests
|
||||
php: 7.2
|
||||
- name: PHP 7.3 Unit Tests
|
||||
php: 7.3
|
||||
- name: PHP 7.4 Unit Tests
|
||||
php: 7.4
|
||||
- name: PHP 8.0 Unit Tests
|
||||
php: nightly
|
||||
install:
|
||||
- composer update --ignore-platform-req=php --no-progress --prefer-dist
|
||||
- name: PHP 7.3 Code on PHP 8.0 Integration Tests
|
||||
php: nightly
|
||||
install:
|
||||
- composer update --ignore-platform-req=php --no-progress --prefer-dist
|
||||
script:
|
||||
- test_old/run-php-src.sh 7.3.21
|
||||
- name: PHP 8.0 Code on PHP 7.0 Integration Tests
|
||||
php: 7.0
|
||||
script:
|
||||
- test_old/run-php-src.sh 8.0.0beta4
|
||||
allow_failures:
|
||||
- name: PHP 8.0 Code on PHP 7.0 Integration Tests
|
||||
fast_finish: true
|
||||
|
||||
script: vendor/bin/phpunit
|
135
CHANGELOG.md
135
CHANGELOG.md
@ -1,8 +1,141 @@
|
||||
Version 4.10.1-dev
|
||||
Version 4.13.3-dev
|
||||
------------------
|
||||
|
||||
Nothing yet.
|
||||
|
||||
Version 4.13.2 (2021-11-30)
|
||||
---------------------------
|
||||
|
||||
### Added
|
||||
|
||||
* Added builders for enums and enum cases.
|
||||
|
||||
### Fixed
|
||||
|
||||
* NullsafeMethodCall now extends from CallLike.
|
||||
* The `namespacedName` property populated by the `NameResolver` is now declared on relevant nodes,
|
||||
to avoid a dynamic property deprecation warning with PHP 8.2.
|
||||
|
||||
Version 4.13.1 (2021-11-03)
|
||||
---------------------------
|
||||
|
||||
### Fixed
|
||||
|
||||
* Support reserved keywords as enum cases.
|
||||
* Support array unpacking in constant expression evaluator.
|
||||
|
||||
Version 4.13.0 (2021-09-20)
|
||||
---------------------------
|
||||
|
||||
### Added
|
||||
|
||||
* [PHP 8.1] Added support for intersection types using a new `IntersectionType` node. Additionally
|
||||
a `ComplexType` parent class for `NullableType`, `UnionType` and `IntersectionType` has been
|
||||
added.
|
||||
* [PHP 8.1] Added support for explicit octal literals.
|
||||
* [PHP 8.1] Added support for first-class callables. These are represented using a call whose first
|
||||
argument is a `VariadicPlaceholder`. The representation is intended to be forward-compatible with
|
||||
partial function application, just like the PHP feature itself. Call nodes now extend from
|
||||
`Expr\CallLike`, which provides an `isFirstClassCallable()` method to determine whether a
|
||||
placeholder id present. `getArgs()` can be used to assert that the call is not a first-class
|
||||
callable and returns `Arg[]` rather than `array<Arg|VariadicPlaceholder>`.
|
||||
|
||||
### Fixed
|
||||
|
||||
* Multiple modifiers for promoted properties are now accepted. In particular this allows something
|
||||
like `public readonly` for promoted properties.
|
||||
* Formatting-preserving pretty printing for comments in array literals has been fixed.
|
||||
|
||||
Version 4.12.0 (2021-07-21)
|
||||
---------------------------
|
||||
|
||||
### Added
|
||||
|
||||
* [PHP 8.1] Added support for readonly properties (through a new `MODIFIER_READONLY`).
|
||||
* [PHP 8.1] Added support for final class constants.
|
||||
|
||||
### Fixed
|
||||
|
||||
* Fixed compatibility with PHP 8.1. `&` tokens are now canonicalized to the
|
||||
`T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG` and `T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG` tokens
|
||||
used in PHP 8.1. This happens unconditionally, regardless of whether the emulative lexer is used.
|
||||
|
||||
Version 4.11.0 (2021-07-03)
|
||||
---------------------------
|
||||
|
||||
### Added
|
||||
|
||||
* `BuilderFactory::args()` now accepts named arguments.
|
||||
* `BuilderFactory::attribute()` has been added.
|
||||
* An `addAttribute()` method accepting an `Attribute` or `AttributeGroup` has been adde to all
|
||||
builders that accept attributes, such as `Builder\Class_`.
|
||||
|
||||
### Fixed
|
||||
|
||||
* `NameResolver` now handles enums.
|
||||
* `PrettyPrinter` now prints backing enum type.
|
||||
* Builder methods for types now property handle `never` type.
|
||||
|
||||
Version 4.10.5 (2021-05-03)
|
||||
---------------------------
|
||||
|
||||
### Added
|
||||
|
||||
* [PHP 8.1] Added support for enums. These are represented using the `Stmt\Enum_` and
|
||||
`Stmt\EnumCase` nodes.
|
||||
* [PHP 8.1] Added support for never type. This type will now be returned as an `Identifier` rather
|
||||
than `Name`.
|
||||
* Added `ClassConst` builder.
|
||||
|
||||
### Changed
|
||||
|
||||
* Non-UTF-8 code units in strings will now be hex-encoded.
|
||||
|
||||
### Fixed
|
||||
|
||||
* Fixed precedence of arrow functions.
|
||||
|
||||
Version 4.10.4 (2020-12-20)
|
||||
---------------------------
|
||||
|
||||
### Fixed
|
||||
|
||||
* Fixed position information for variable-variables (#741).
|
||||
* Fixed position information for traits/interfaces preceded by if statement (#738).
|
||||
|
||||
Version 4.10.3 (2020-12-03)
|
||||
---------------------------
|
||||
|
||||
### Fixed
|
||||
|
||||
* Fixed formatting-preserving pretty printing for `"{$x}"`.
|
||||
* Ternary expressions are now treated as non-associative in the pretty printer, in order to
|
||||
generate code that is compatible with the parentheses requirement introduced in PHP 8.
|
||||
* Removed no longer necessary `error_clear_last()` call in lexer, which may interfere with fatal
|
||||
error handlers if invoked during shutdown.
|
||||
|
||||
|
||||
Version 4.10.2 (2020-09-26)
|
||||
------------------
|
||||
|
||||
### Fixed
|
||||
|
||||
* Fixed check for token emulation conflicts with other libraries.
|
||||
|
||||
Version 4.10.1 (2020-09-23)
|
||||
---------------------------
|
||||
|
||||
### Added
|
||||
|
||||
* Added support for recovering from a missing semicolon after a property or class constant
|
||||
declaration.
|
||||
|
||||
### Fixed
|
||||
|
||||
* Fix spurious whitespace in formatting-preserving pretty printer when both removing and adding
|
||||
elements at the start of a list.
|
||||
* Fix incorrect case-sensitivity in keyword token emulation.
|
||||
|
||||
Version 4.10.0 (2020-09-19)
|
||||
---------------------------
|
||||
|
||||
|
10
README.md
10
README.md
@ -1,12 +1,12 @@
|
||||
PHP Parser
|
||||
==========
|
||||
|
||||
[](https://travis-ci.org/nikic/PHP-Parser) [](https://coveralls.io/github/nikic/PHP-Parser?branch=master)
|
||||
[](https://coveralls.io/github/nikic/PHP-Parser?branch=master)
|
||||
|
||||
This is a PHP 5.2 to PHP 7.4 parser written in PHP. Its purpose is to simplify static code analysis and
|
||||
This is a PHP 5.2 to PHP 8.0 parser written in PHP. Its purpose is to simplify static code analysis and
|
||||
manipulation.
|
||||
|
||||
[**Documentation for version 4.x**][doc_master] (stable; for running on PHP >= 7.0; for parsing PHP 5.2 to PHP 7.4).
|
||||
[**Documentation for version 4.x**][doc_master] (stable; for running on PHP >= 7.0; for parsing PHP 5.2 to PHP 8.0).
|
||||
|
||||
[Documentation for version 3.x][doc_3_x] (unsupported; for running on PHP >= 5.5; for parsing PHP 5.2 to PHP 7.2).
|
||||
|
||||
@ -15,7 +15,7 @@ Features
|
||||
|
||||
The main features provided by this library are:
|
||||
|
||||
* Parsing PHP 5 and PHP 7 code into an abstract syntax tree (AST).
|
||||
* Parsing PHP 5, PHP 7, and PHP 8 code into an abstract syntax tree (AST).
|
||||
* Invalid code can be parsed into a partial AST.
|
||||
* The AST contains accurate location information.
|
||||
* Dumping the AST in human-readable form.
|
||||
@ -215,7 +215,7 @@ Component documentation:
|
||||
* [JSON representation](doc/component/JSON_representation.markdown)
|
||||
* JSON encoding and decoding of ASTs
|
||||
* [Performance](doc/component/Performance.markdown)
|
||||
* Disabling XDebug
|
||||
* Disabling Xdebug
|
||||
* Reusing objects
|
||||
* Garbage collection impact
|
||||
* [Frequently asked questions](doc/component/FAQ.markdown)
|
||||
|
@ -10,7 +10,7 @@ foreach ([__DIR__ . '/../../../autoload.php', __DIR__ . '/../vendor/autoload.php
|
||||
|
||||
ini_set('xdebug.max_nesting_level', 3000);
|
||||
|
||||
// Disable XDebug var_dump() output truncation
|
||||
// Disable Xdebug var_dump() output truncation
|
||||
ini_set('xdebug.var_display_max_children', -1);
|
||||
ini_set('xdebug.var_display_max_data', -1);
|
||||
ini_set('xdebug.var_display_max_depth', -1);
|
||||
|
@ -1,7 +1,7 @@
|
||||
Introduction
|
||||
============
|
||||
|
||||
This project is a PHP 5.2 to PHP 7.4 parser **written in PHP itself**.
|
||||
This project is a PHP 5.2 to PHP 8.0 parser **written in PHP itself**.
|
||||
|
||||
What is this for?
|
||||
-----------------
|
||||
@ -26,7 +26,11 @@ programmatic PHP code analysis are incidentally PHP developers, not C developers
|
||||
What can it parse?
|
||||
------------------
|
||||
|
||||
The parser supports parsing PHP 5.2-7.4.
|
||||
The parser supports parsing PHP 5.2-8.0, with the following exceptions:
|
||||
|
||||
* Namespaced names containing whitespace (e.g. `Foo \ Bar` instead of `Foo\Bar`) are not supported.
|
||||
These are illegal in PHP 8, but are legal in earlier version. However, PHP-Parser does not
|
||||
support them for any version.
|
||||
|
||||
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.
|
||||
|
@ -19,7 +19,7 @@ ini_set('xdebug.max_nesting_level', 3000);
|
||||
```
|
||||
|
||||
This ensures that there will be no errors when traversing highly nested node trees. However, it is
|
||||
preferable to disable XDebug completely, as it can easily make this library more than five times
|
||||
preferable to disable Xdebug completely, as it can easily make this library more than five times
|
||||
slower.
|
||||
|
||||
Parsing
|
||||
|
@ -39,7 +39,7 @@ Component documentation
|
||||
* [JSON representation](component/JSON_representation.markdown)
|
||||
* JSON encoding and decoding of ASTs
|
||||
* [Performance](component/Performance.markdown)
|
||||
* Disabling XDebug
|
||||
* Disabling Xdebug
|
||||
* Reusing objects
|
||||
* Garbage collection impact
|
||||
* [Frequently asked questions](component/FAQ.markdown)
|
||||
|
@ -36,7 +36,7 @@ if ($e->hasColumnInfo()) {
|
||||
echo $e->getRawMessage() . ' from ' . $e->getStartLine() . ':' . $e->getStartColumn($code)
|
||||
. ' to ' . $e->getEndLine() . ':' . $e->getEndColumn($code);
|
||||
// or:
|
||||
echo $e->getMessageWithColumnInfo();
|
||||
echo $e->getMessageWithColumnInfo($code);
|
||||
} else {
|
||||
echo $e->getMessage();
|
||||
}
|
||||
@ -72,4 +72,4 @@ if (null !== $stmts) {
|
||||
}
|
||||
```
|
||||
|
||||
The `NameResolver` visitor also accepts an `ErrorHandler` as a constructor argument.
|
||||
The `NameResolver` visitor also accepts an `ErrorHandler` as a constructor argument.
|
||||
|
@ -8,20 +8,20 @@ described in the following.
|
||||
Xdebug
|
||||
------
|
||||
|
||||
Running PHP with XDebug adds a lot of overhead, especially for code that performs many method calls.
|
||||
Just by loading XDebug (without enabling profiling or other more intrusive XDebug features), you
|
||||
Running PHP with Xdebug adds a lot of overhead, especially for code that performs many method calls.
|
||||
Just by loading Xdebug (without enabling profiling or other more intrusive Xdebug features), you
|
||||
can expect that code using PHP-Parser will be approximately *five times slower*.
|
||||
|
||||
As such, you should make sure that XDebug is not loaded when using this library. Note that setting
|
||||
the `xdebug.default_enable=0` ini option does *not* disable XDebug. The *only* way to disable
|
||||
XDebug is to not load the extension in the first place.
|
||||
As such, you should make sure that Xdebug is not loaded when using this library. Note that setting
|
||||
the `xdebug.default_enable=0` ini option does *not* disable Xdebug. The *only* way to disable
|
||||
Xdebug is to not load the extension in the first place.
|
||||
|
||||
If you are building a command-line utility for use by developers (who often have XDebug enabled),
|
||||
you may want to consider automatically restarting PHP with XDebug unloaded. The
|
||||
If you are building a command-line utility for use by developers (who often have Xdebug enabled),
|
||||
you may want to consider automatically restarting PHP with Xdebug unloaded. The
|
||||
[composer/xdebug-handler](https://github.com/composer/xdebug-handler) package can be used to do
|
||||
this.
|
||||
|
||||
If you do run with XDebug, you may need to increase the `xdebug.max_nesting_level` option to a
|
||||
If you do run with Xdebug, you may need to increase the `xdebug.max_nesting_level` option to a
|
||||
higher level, such as 3000. While the parser itself is recursion free, most other code working on
|
||||
the AST uses recursion and will generate an error if the value of this option is too low.
|
||||
|
||||
@ -62,4 +62,4 @@ disproportionally (and are usually generated anyway).
|
||||
Of course, you can also try to (temporarily) disable GC. By design the AST generated by PHP-Parser
|
||||
is cycle-free, so the AST itself will never cause leaks with GC disabled. However, other code
|
||||
(including for example the parser object itself) may hold cycles, so disabling of GC should be
|
||||
approached with care.
|
||||
approached with care.
|
||||
|
@ -193,7 +193,7 @@ anonymous classes), you know that once you've seen a class declaration, there is
|
||||
checking all it's child nodes, because PHP does not allow nesting classes. In this case, you can
|
||||
instruct the traverser to not recurse into the class node:
|
||||
|
||||
```
|
||||
```php
|
||||
private $classes = [];
|
||||
public function enterNode(Node $node) {
|
||||
if ($node instanceof Node\Stmt\Class_) {
|
||||
@ -211,7 +211,7 @@ after finding it. For example, if you are looking for the node of a class with a
|
||||
discounting exotic cases like conditionally defining a class two times), you can stop traversal
|
||||
once you found it:
|
||||
|
||||
```
|
||||
```php
|
||||
private $class = null;
|
||||
public function enterNode(Node $node) {
|
||||
if ($node instanceof Node\Stmt\Class_ &&
|
||||
@ -251,7 +251,7 @@ Stmt_Return(
|
||||
|
||||
the following method calls will be performed:
|
||||
|
||||
```
|
||||
```php
|
||||
$visitorA->enterNode(Stmt_Return)
|
||||
$visitorB->enterNode(Stmt_Return)
|
||||
$visitorA->enterNode(Expr_Variable)
|
||||
|
@ -20,6 +20,11 @@ top_statement_list:
|
||||
if ($nop !== null) { $1[] = $nop; } $$ = $1; }
|
||||
;
|
||||
|
||||
ampersand:
|
||||
T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG
|
||||
| T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG
|
||||
;
|
||||
|
||||
reserved_non_modifiers:
|
||||
T_INCLUDE | T_INCLUDE_ONCE | T_EVAL | T_REQUIRE | T_REQUIRE_ONCE | T_LOGICAL_OR | T_LOGICAL_XOR | T_LOGICAL_AND
|
||||
| T_INSTANCEOF | T_NEW | T_CLONE | T_EXIT | T_IF | T_ELSEIF | T_ELSE | T_ENDIF | T_ECHO | T_DO | T_WHILE
|
||||
@ -246,7 +251,12 @@ variables_list:
|
||||
|
||||
optional_ref:
|
||||
/* empty */ { $$ = false; }
|
||||
| '&' { $$ = true; }
|
||||
| ampersand { $$ = true; }
|
||||
;
|
||||
|
||||
optional_arg_ref:
|
||||
/* empty */ { $$ = false; }
|
||||
| T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG { $$ = true; }
|
||||
;
|
||||
|
||||
optional_ellipsis:
|
||||
@ -378,7 +388,7 @@ new_else_single:
|
||||
|
||||
foreach_variable:
|
||||
variable { $$ = array($1, false); }
|
||||
| '&' variable { $$ = array($2, true); }
|
||||
| ampersand variable { $$ = array($2, true); }
|
||||
| list_expr { $$ = array($1, false); }
|
||||
;
|
||||
|
||||
@ -393,9 +403,9 @@ non_empty_parameter_list:
|
||||
;
|
||||
|
||||
parameter:
|
||||
optional_param_type optional_ref optional_ellipsis plain_variable
|
||||
optional_param_type optional_arg_ref optional_ellipsis plain_variable
|
||||
{ $$ = Node\Param[$4, null, $1, $2, $3]; $this->checkParam($$); }
|
||||
| optional_param_type optional_ref optional_ellipsis plain_variable '=' static_scalar
|
||||
| optional_param_type optional_arg_ref optional_ellipsis plain_variable '=' static_scalar
|
||||
{ $$ = Node\Param[$4, $6, $1, $2, $3]; $this->checkParam($$); }
|
||||
;
|
||||
|
||||
@ -428,7 +438,7 @@ non_empty_argument_list:
|
||||
|
||||
argument:
|
||||
expr { $$ = Node\Arg[$1, false, false]; }
|
||||
| '&' variable { $$ = Node\Arg[$2, true, false]; }
|
||||
| ampersand variable { $$ = Node\Arg[$2, true, false]; }
|
||||
| T_ELLIPSIS expr { $$ = Node\Arg[$2, false, true]; }
|
||||
;
|
||||
|
||||
@ -562,8 +572,8 @@ expr:
|
||||
variable { $$ = $1; }
|
||||
| list_expr '=' expr { $$ = Expr\Assign[$1, $3]; }
|
||||
| variable '=' expr { $$ = Expr\Assign[$1, $3]; }
|
||||
| variable '=' '&' variable { $$ = Expr\AssignRef[$1, $4]; }
|
||||
| variable '=' '&' new_expr { $$ = Expr\AssignRef[$1, $4]; }
|
||||
| variable '=' ampersand variable { $$ = Expr\AssignRef[$1, $4]; }
|
||||
| variable '=' ampersand new_expr { $$ = Expr\AssignRef[$1, $4]; }
|
||||
| new_expr { $$ = $1; }
|
||||
| T_CLONE expr { $$ = Expr\Clone_[$2]; }
|
||||
| variable T_PLUS_EQUAL expr { $$ = Expr\AssignOp\Plus [$1, $3]; }
|
||||
@ -589,7 +599,8 @@ expr:
|
||||
| expr T_LOGICAL_AND expr { $$ = Expr\BinaryOp\LogicalAnd[$1, $3]; }
|
||||
| expr T_LOGICAL_XOR expr { $$ = Expr\BinaryOp\LogicalXor[$1, $3]; }
|
||||
| expr '|' expr { $$ = Expr\BinaryOp\BitwiseOr [$1, $3]; }
|
||||
| expr '&' expr { $$ = Expr\BinaryOp\BitwiseAnd[$1, $3]; }
|
||||
| expr T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG expr { $$ = Expr\BinaryOp\BitwiseAnd[$1, $3]; }
|
||||
| expr T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG expr { $$ = Expr\BinaryOp\BitwiseAnd[$1, $3]; }
|
||||
| expr '^' expr { $$ = Expr\BinaryOp\BitwiseXor[$1, $3]; }
|
||||
| expr '.' expr { $$ = Expr\BinaryOp\Concat [$1, $3]; }
|
||||
| expr '+' expr { $$ = Expr\BinaryOp\Plus [$1, $3]; }
|
||||
@ -816,7 +827,10 @@ static_operation:
|
||||
| static_scalar T_LOGICAL_AND static_scalar { $$ = Expr\BinaryOp\LogicalAnd[$1, $3]; }
|
||||
| static_scalar T_LOGICAL_XOR static_scalar { $$ = Expr\BinaryOp\LogicalXor[$1, $3]; }
|
||||
| static_scalar '|' static_scalar { $$ = Expr\BinaryOp\BitwiseOr [$1, $3]; }
|
||||
| static_scalar '&' static_scalar { $$ = Expr\BinaryOp\BitwiseAnd[$1, $3]; }
|
||||
| static_scalar T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG static_scalar
|
||||
{ $$ = Expr\BinaryOp\BitwiseAnd[$1, $3]; }
|
||||
| static_scalar T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG static_scalar
|
||||
{ $$ = Expr\BinaryOp\BitwiseAnd[$1, $3]; }
|
||||
| static_scalar '^' static_scalar { $$ = Expr\BinaryOp\BitwiseXor[$1, $3]; }
|
||||
| static_scalar '.' static_scalar { $$ = Expr\BinaryOp\Concat [$1, $3]; }
|
||||
| static_scalar '+' static_scalar { $$ = Expr\BinaryOp\Plus [$1, $3]; }
|
||||
@ -986,8 +1000,8 @@ non_empty_array_pair_list:
|
||||
array_pair:
|
||||
expr T_DOUBLE_ARROW expr { $$ = Expr\ArrayItem[$3, $1, false]; }
|
||||
| expr { $$ = Expr\ArrayItem[$1, null, false]; }
|
||||
| expr T_DOUBLE_ARROW '&' variable { $$ = Expr\ArrayItem[$4, $1, true]; }
|
||||
| '&' variable { $$ = Expr\ArrayItem[$2, null, true]; }
|
||||
| expr T_DOUBLE_ARROW ampersand variable { $$ = Expr\ArrayItem[$4, $1, true]; }
|
||||
| ampersand variable { $$ = Expr\ArrayItem[$2, null, true]; }
|
||||
| T_ELLIPSIS expr { $$ = Expr\ArrayItem[$2, null, false, attributes(), true]; }
|
||||
;
|
||||
|
||||
|
169
grammar/php7.y
169
grammar/php7.y
@ -20,6 +20,11 @@ top_statement_list:
|
||||
if ($nop !== null) { $1[] = $nop; } $$ = $1; }
|
||||
;
|
||||
|
||||
ampersand:
|
||||
T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG
|
||||
| T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG
|
||||
;
|
||||
|
||||
reserved_non_modifiers:
|
||||
T_INCLUDE | T_INCLUDE_ONCE | T_EVAL | T_REQUIRE | T_REQUIRE_ONCE | T_LOGICAL_OR | T_LOGICAL_XOR | T_LOGICAL_AND
|
||||
| T_INSTANCEOF | T_NEW | T_CLONE | T_EXIT | T_IF | T_ELSEIF | T_ELSE | T_ENDIF | T_ECHO | T_DO | T_WHILE
|
||||
@ -28,20 +33,20 @@ reserved_non_modifiers:
|
||||
| T_FUNCTION | T_CONST | T_RETURN | T_PRINT | T_YIELD | T_LIST | T_SWITCH | T_ENDSWITCH | T_CASE | T_DEFAULT
|
||||
| T_BREAK | T_ARRAY | T_CALLABLE | T_EXTENDS | T_IMPLEMENTS | T_NAMESPACE | T_TRAIT | T_INTERFACE | T_CLASS
|
||||
| T_CLASS_C | T_TRAIT_C | T_FUNC_C | T_METHOD_C | T_LINE | T_FILE | T_DIR | T_NS_C | T_HALT_COMPILER | T_FN
|
||||
| T_MATCH
|
||||
| T_MATCH | T_ENUM
|
||||
;
|
||||
|
||||
semi_reserved:
|
||||
reserved_non_modifiers
|
||||
| T_STATIC | T_ABSTRACT | T_FINAL | T_PRIVATE | T_PROTECTED | T_PUBLIC
|
||||
| T_STATIC | T_ABSTRACT | T_FINAL | T_PRIVATE | T_PROTECTED | T_PUBLIC | T_READONLY
|
||||
;
|
||||
|
||||
identifier_ex:
|
||||
identifier_maybe_reserved:
|
||||
T_STRING { $$ = Node\Identifier[$1]; }
|
||||
| semi_reserved { $$ = Node\Identifier[$1]; }
|
||||
;
|
||||
|
||||
identifier:
|
||||
identifier_not_reserved:
|
||||
T_STRING { $$ = Node\Identifier[$1]; }
|
||||
;
|
||||
|
||||
@ -176,14 +181,14 @@ non_empty_inline_use_declarations:
|
||||
unprefixed_use_declaration:
|
||||
namespace_name
|
||||
{ $$ = Stmt\UseUse[$1, null, Stmt\Use_::TYPE_UNKNOWN]; $this->checkUseUse($$, #1); }
|
||||
| namespace_name T_AS identifier
|
||||
| namespace_name T_AS identifier_not_reserved
|
||||
{ $$ = Stmt\UseUse[$1, $3, Stmt\Use_::TYPE_UNKNOWN]; $this->checkUseUse($$, #3); }
|
||||
;
|
||||
|
||||
use_declaration:
|
||||
legacy_namespace_name
|
||||
{ $$ = Stmt\UseUse[$1, null, Stmt\Use_::TYPE_UNKNOWN]; $this->checkUseUse($$, #1); }
|
||||
| legacy_namespace_name T_AS identifier
|
||||
| legacy_namespace_name T_AS identifier_not_reserved
|
||||
{ $$ = Stmt\UseUse[$1, $3, Stmt\Use_::TYPE_UNKNOWN]; $this->checkUseUse($$, #3); }
|
||||
;
|
||||
|
||||
@ -203,7 +208,7 @@ non_empty_constant_declaration_list:
|
||||
;
|
||||
|
||||
constant_declaration:
|
||||
identifier '=' expr { $$ = Node\Const_[$1, $3]; }
|
||||
identifier_not_reserved '=' expr { $$ = Node\Const_[$1, $3]; }
|
||||
;
|
||||
|
||||
class_const_list:
|
||||
@ -216,7 +221,7 @@ non_empty_class_const_list:
|
||||
;
|
||||
|
||||
class_const:
|
||||
identifier_ex '=' expr { $$ = Node\Const_[$1, $3]; }
|
||||
identifier_maybe_reserved '=' expr { $$ = Node\Const_[$1, $3]; }
|
||||
;
|
||||
|
||||
inner_statement_list_ex:
|
||||
@ -284,8 +289,8 @@ non_empty_statement:
|
||||
| T_DECLARE '(' declare_list ')' declare_statement { $$ = Stmt\Declare_[$3, $5]; }
|
||||
| T_TRY '{' inner_statement_list '}' catches optional_finally
|
||||
{ $$ = Stmt\TryCatch[$3, $5, $6]; $this->checkTryCatch($$); }
|
||||
| T_GOTO identifier semi { $$ = Stmt\Goto_[$2]; }
|
||||
| identifier ':' { $$ = Stmt\Label[$1]; }
|
||||
| T_GOTO identifier_not_reserved semi { $$ = Stmt\Goto_[$2]; }
|
||||
| identifier_not_reserved ':' { $$ = Stmt\Label[$1]; }
|
||||
| error { $$ = array(); /* means: no statement */ }
|
||||
;
|
||||
|
||||
@ -327,7 +332,12 @@ non_empty_variables_list:
|
||||
|
||||
optional_ref:
|
||||
/* empty */ { $$ = false; }
|
||||
| '&' { $$ = true; }
|
||||
| ampersand { $$ = true; }
|
||||
;
|
||||
|
||||
optional_arg_ref:
|
||||
/* empty */ { $$ = false; }
|
||||
| T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG { $$ = true; }
|
||||
;
|
||||
|
||||
optional_ellipsis:
|
||||
@ -341,24 +351,33 @@ block_or_error:
|
||||
;
|
||||
|
||||
function_declaration_statement:
|
||||
T_FUNCTION optional_ref identifier '(' parameter_list ')' optional_return_type block_or_error
|
||||
T_FUNCTION optional_ref identifier_not_reserved '(' 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 '(' parameter_list ')' optional_return_type block_or_error
|
||||
| attributes T_FUNCTION optional_ref identifier_not_reserved '(' parameter_list ')' optional_return_type block_or_error
|
||||
{ $$ = Stmt\Function_[$4, ['byRef' => $3, 'params' => $6, 'returnType' => $8, 'stmts' => $9, 'attrGroups' => $1]]; }
|
||||
;
|
||||
|
||||
class_declaration_statement:
|
||||
class_entry_type identifier extends_from implements_list '{' class_statement_list '}'
|
||||
{ $$ = Stmt\Class_[$2, ['type' => $1, 'extends' => $3, 'implements' => $4, 'stmts' => $6, 'attrGroups' => []]];
|
||||
$this->checkClass($$, #2); }
|
||||
| attributes class_entry_type identifier extends_from implements_list '{' class_statement_list '}'
|
||||
optional_attributes class_entry_type identifier_not_reserved extends_from implements_list '{' class_statement_list '}'
|
||||
{ $$ = Stmt\Class_[$3, ['type' => $2, 'extends' => $4, 'implements' => $5, 'stmts' => $7, 'attrGroups' => $1]];
|
||||
$this->checkClass($$, #3); }
|
||||
| optional_attributes T_INTERFACE identifier interface_extends_list '{' class_statement_list '}'
|
||||
| optional_attributes T_INTERFACE identifier_not_reserved interface_extends_list '{' class_statement_list '}'
|
||||
{ $$ = Stmt\Interface_[$3, ['extends' => $4, 'stmts' => $6, 'attrGroups' => $1]];
|
||||
$this->checkInterface($$, #3); }
|
||||
| optional_attributes T_TRAIT identifier '{' class_statement_list '}'
|
||||
| optional_attributes T_TRAIT identifier_not_reserved '{' class_statement_list '}'
|
||||
{ $$ = Stmt\Trait_[$3, ['stmts' => $5, 'attrGroups' => $1]]; }
|
||||
| optional_attributes T_ENUM identifier_not_reserved enum_scalar_type implements_list '{' class_statement_list '}'
|
||||
{ $$ = Stmt\Enum_[$3, ['scalarType' => $4, 'implements' => $5, 'stmts' => $7, 'attrGroups' => $1]];
|
||||
$this->checkEnum($$, #3); }
|
||||
;
|
||||
|
||||
enum_scalar_type:
|
||||
/* empty */ { $$ = null; }
|
||||
| ':' type { $$ = $2; }
|
||||
|
||||
enum_case_expr:
|
||||
/* empty */ { $$ = null; }
|
||||
| '=' expr { $$ = $2; }
|
||||
;
|
||||
|
||||
class_entry_type:
|
||||
@ -417,7 +436,7 @@ non_empty_declare_list:
|
||||
;
|
||||
|
||||
declare_list_element:
|
||||
identifier '=' expr { $$ = Stmt\DeclareDeclare[$1, $3]; }
|
||||
identifier_not_reserved '=' expr { $$ = Stmt\DeclareDeclare[$1, $3]; }
|
||||
;
|
||||
|
||||
switch_case_list:
|
||||
@ -496,7 +515,7 @@ new_else_single:
|
||||
|
||||
foreach_variable:
|
||||
variable { $$ = array($1, false); }
|
||||
| '&' variable { $$ = array($2, true); }
|
||||
| ampersand variable { $$ = array($2, true); }
|
||||
| list_expr { $$ = array($1, false); }
|
||||
| array_short_syntax { $$ = array($1, false); }
|
||||
;
|
||||
@ -511,21 +530,30 @@ non_empty_parameter_list:
|
||||
| non_empty_parameter_list ',' parameter { push($1, $3); }
|
||||
;
|
||||
|
||||
optional_visibility_modifier:
|
||||
optional_property_modifiers:
|
||||
/* empty */ { $$ = 0; }
|
||||
| T_PUBLIC { $$ = Stmt\Class_::MODIFIER_PUBLIC; }
|
||||
| optional_property_modifiers property_modifier
|
||||
{ $this->checkModifier($1, $2, #2); $$ = $1 | $2; }
|
||||
;
|
||||
|
||||
property_modifier:
|
||||
T_PUBLIC { $$ = Stmt\Class_::MODIFIER_PUBLIC; }
|
||||
| T_PROTECTED { $$ = Stmt\Class_::MODIFIER_PROTECTED; }
|
||||
| T_PRIVATE { $$ = Stmt\Class_::MODIFIER_PRIVATE; }
|
||||
| T_READONLY { $$ = Stmt\Class_::MODIFIER_READONLY; }
|
||||
;
|
||||
|
||||
parameter:
|
||||
optional_attributes optional_visibility_modifier optional_type_without_static optional_ref optional_ellipsis plain_variable
|
||||
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_attributes optional_visibility_modifier optional_type_without_static optional_ref optional_ellipsis plain_variable '=' expr
|
||||
| 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_attributes optional_visibility_modifier optional_type_without_static optional_ref optional_ellipsis error
|
||||
| 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); }
|
||||
;
|
||||
|
||||
@ -533,6 +561,7 @@ type_expr:
|
||||
type { $$ = $1; }
|
||||
| '?' type { $$ = Node\NullableType[$2]; }
|
||||
| union_type { $$ = Node\UnionType[$1]; }
|
||||
| intersection_type { $$ = Node\IntersectionType[$1]; }
|
||||
;
|
||||
|
||||
type:
|
||||
@ -556,10 +585,24 @@ union_type_without_static:
|
||||
| union_type_without_static '|' type_without_static { push($1, $3); }
|
||||
;
|
||||
|
||||
intersection_type:
|
||||
type T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG type { init($1, $3); }
|
||||
| intersection_type T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG type
|
||||
{ push($1, $3); }
|
||||
;
|
||||
|
||||
intersection_type_without_static:
|
||||
type_without_static T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG type_without_static
|
||||
{ init($1, $3); }
|
||||
| intersection_type_without_static T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG type_without_static
|
||||
{ push($1, $3); }
|
||||
;
|
||||
|
||||
type_expr_without_static:
|
||||
type_without_static { $$ = $1; }
|
||||
| '?' type_without_static { $$ = Node\NullableType[$2]; }
|
||||
| union_type_without_static { $$ = Node\UnionType[$1]; }
|
||||
| intersection_type_without_static { $$ = Node\IntersectionType[$1]; }
|
||||
;
|
||||
|
||||
optional_type_without_static:
|
||||
@ -576,6 +619,11 @@ optional_return_type:
|
||||
argument_list:
|
||||
'(' ')' { $$ = array(); }
|
||||
| '(' non_empty_argument_list optional_comma ')' { $$ = $2; }
|
||||
| '(' variadic_placeholder ')' { init($2); }
|
||||
;
|
||||
|
||||
variadic_placeholder:
|
||||
T_ELLIPSIS { $$ = Node\VariadicPlaceholder[]; }
|
||||
;
|
||||
|
||||
non_empty_argument_list:
|
||||
@ -585,9 +633,9 @@ non_empty_argument_list:
|
||||
|
||||
argument:
|
||||
expr { $$ = Node\Arg[$1, false, false]; }
|
||||
| '&' variable { $$ = Node\Arg[$2, true, false]; }
|
||||
| ampersand variable { $$ = Node\Arg[$2, true, false]; }
|
||||
| T_ELLIPSIS expr { $$ = Node\Arg[$2, false, true]; }
|
||||
| identifier_ex ':' expr
|
||||
| identifier_maybe_reserved ':' expr
|
||||
{ $$ = new Node\Arg($3, false, false, attributes(), $1); }
|
||||
;
|
||||
|
||||
@ -601,7 +649,7 @@ non_empty_global_var_list:
|
||||
;
|
||||
|
||||
global_var:
|
||||
simple_variable { $$ = Expr\Variable[$1]; }
|
||||
simple_variable { $$ = $1; }
|
||||
;
|
||||
|
||||
static_var_list:
|
||||
@ -630,16 +678,19 @@ class_statement_list:
|
||||
;
|
||||
|
||||
class_statement:
|
||||
optional_attributes variable_modifiers optional_type_without_static property_declaration_list ';'
|
||||
optional_attributes variable_modifiers optional_type_without_static property_declaration_list semi
|
||||
{ $$ = new Stmt\Property($2, $4, attributes(), $3, $1);
|
||||
$this->checkProperty($$, #2); }
|
||||
| optional_attributes method_modifiers T_CONST class_const_list ';'
|
||||
| optional_attributes method_modifiers T_CONST class_const_list semi
|
||||
{ $$ = new Stmt\ClassConst($4, $2, attributes(), $1);
|
||||
$this->checkClassConst($$, #2); }
|
||||
| optional_attributes method_modifiers T_FUNCTION optional_ref identifier_ex '(' parameter_list ')' optional_return_type method_body
|
||||
| optional_attributes method_modifiers T_FUNCTION optional_ref identifier_maybe_reserved '(' parameter_list ')'
|
||||
optional_return_type method_body
|
||||
{ $$ = Stmt\ClassMethod[$5, ['type' => $2, 'byRef' => $4, 'params' => $7, 'returnType' => $9, 'stmts' => $10, 'attrGroups' => $1]];
|
||||
$this->checkClassMethod($$, #2); }
|
||||
| T_USE class_name_list trait_adaptations { $$ = Stmt\TraitUse[$2, $3]; }
|
||||
| optional_attributes T_CASE identifier_maybe_reserved enum_case_expr semi
|
||||
{ $$ = Stmt\EnumCase[$3, $4, $1]; }
|
||||
| error { $$ = null; /* will be skipped */ }
|
||||
;
|
||||
|
||||
@ -656,22 +707,22 @@ trait_adaptation_list:
|
||||
trait_adaptation:
|
||||
trait_method_reference_fully_qualified T_INSTEADOF class_name_list ';'
|
||||
{ $$ = Stmt\TraitUseAdaptation\Precedence[$1[0], $1[1], $3]; }
|
||||
| trait_method_reference T_AS member_modifier identifier_ex ';'
|
||||
| trait_method_reference T_AS member_modifier identifier_maybe_reserved ';'
|
||||
{ $$ = Stmt\TraitUseAdaptation\Alias[$1[0], $1[1], $3, $4]; }
|
||||
| trait_method_reference T_AS member_modifier ';'
|
||||
{ $$ = Stmt\TraitUseAdaptation\Alias[$1[0], $1[1], $3, null]; }
|
||||
| trait_method_reference T_AS identifier ';'
|
||||
| trait_method_reference T_AS identifier_not_reserved ';'
|
||||
{ $$ = Stmt\TraitUseAdaptation\Alias[$1[0], $1[1], null, $3]; }
|
||||
| trait_method_reference T_AS reserved_non_modifiers_identifier ';'
|
||||
{ $$ = Stmt\TraitUseAdaptation\Alias[$1[0], $1[1], null, $3]; }
|
||||
;
|
||||
|
||||
trait_method_reference_fully_qualified:
|
||||
name T_PAAMAYIM_NEKUDOTAYIM identifier_ex { $$ = array($1, $3); }
|
||||
name T_PAAMAYIM_NEKUDOTAYIM identifier_maybe_reserved { $$ = array($1, $3); }
|
||||
;
|
||||
trait_method_reference:
|
||||
trait_method_reference_fully_qualified { $$ = $1; }
|
||||
| identifier_ex { $$ = array(null, $1); }
|
||||
| identifier_maybe_reserved { $$ = array(null, $1); }
|
||||
;
|
||||
|
||||
method_body:
|
||||
@ -701,6 +752,7 @@ member_modifier:
|
||||
| T_STATIC { $$ = Stmt\Class_::MODIFIER_STATIC; }
|
||||
| T_ABSTRACT { $$ = Stmt\Class_::MODIFIER_ABSTRACT; }
|
||||
| T_FINAL { $$ = Stmt\Class_::MODIFIER_FINAL; }
|
||||
| T_READONLY { $$ = Stmt\Class_::MODIFIER_READONLY; }
|
||||
;
|
||||
|
||||
property_declaration_list:
|
||||
@ -745,7 +797,7 @@ expr:
|
||||
| list_expr '=' expr { $$ = Expr\Assign[$1, $3]; }
|
||||
| array_short_syntax '=' expr { $$ = Expr\Assign[$1, $3]; }
|
||||
| variable '=' expr { $$ = Expr\Assign[$1, $3]; }
|
||||
| variable '=' '&' variable { $$ = Expr\AssignRef[$1, $4]; }
|
||||
| variable '=' ampersand variable { $$ = Expr\AssignRef[$1, $4]; }
|
||||
| new_expr { $$ = $1; }
|
||||
| match { $$ = $1; }
|
||||
| T_CLONE expr { $$ = Expr\Clone_[$2]; }
|
||||
@ -772,7 +824,8 @@ expr:
|
||||
| expr T_LOGICAL_AND expr { $$ = Expr\BinaryOp\LogicalAnd[$1, $3]; }
|
||||
| expr T_LOGICAL_XOR expr { $$ = Expr\BinaryOp\LogicalXor[$1, $3]; }
|
||||
| expr '|' expr { $$ = Expr\BinaryOp\BitwiseOr [$1, $3]; }
|
||||
| expr '&' expr { $$ = Expr\BinaryOp\BitwiseAnd[$1, $3]; }
|
||||
| expr T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG expr { $$ = Expr\BinaryOp\BitwiseAnd[$1, $3]; }
|
||||
| expr T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG expr { $$ = Expr\BinaryOp\BitwiseAnd[$1, $3]; }
|
||||
| expr '^' expr { $$ = Expr\BinaryOp\BitwiseXor[$1, $3]; }
|
||||
| expr '.' expr { $$ = Expr\BinaryOp\Concat [$1, $3]; }
|
||||
| expr '+' expr { $$ = Expr\BinaryOp\Plus [$1, $3]; }
|
||||
@ -832,18 +885,18 @@ expr:
|
||||
| T_YIELD_FROM expr { $$ = Expr\YieldFrom[$2]; }
|
||||
| T_THROW expr { $$ = Expr\Throw_[$2]; }
|
||||
|
||||
| T_FN optional_ref '(' parameter_list ')' optional_return_type T_DOUBLE_ARROW expr
|
||||
| T_FN optional_ref '(' parameter_list ')' optional_return_type T_DOUBLE_ARROW expr %prec T_THROW
|
||||
{ $$ = Expr\ArrowFunction[['static' => false, 'byRef' => $2, 'params' => $4, 'returnType' => $6, 'expr' => $8, 'attrGroups' => []]]; }
|
||||
| T_STATIC T_FN optional_ref '(' parameter_list ')' optional_return_type T_DOUBLE_ARROW expr
|
||||
| T_STATIC T_FN optional_ref '(' parameter_list ')' optional_return_type T_DOUBLE_ARROW expr %prec T_THROW
|
||||
{ $$ = Expr\ArrowFunction[['static' => true, 'byRef' => $3, 'params' => $5, 'returnType' => $7, 'expr' => $9, 'attrGroups' => []]]; }
|
||||
| T_FUNCTION optional_ref '(' parameter_list ')' lexical_vars optional_return_type block_or_error
|
||||
{ $$ = Expr\Closure[['static' => false, 'byRef' => $2, 'params' => $4, 'uses' => $6, 'returnType' => $7, 'stmts' => $8, 'attrGroups' => []]]; }
|
||||
| T_STATIC T_FUNCTION optional_ref '(' parameter_list ')' lexical_vars optional_return_type block_or_error
|
||||
{ $$ = Expr\Closure[['static' => true, 'byRef' => $3, 'params' => $5, 'uses' => $7, 'returnType' => $8, 'stmts' => $9, 'attrGroups' => []]]; }
|
||||
|
||||
| attributes T_FN optional_ref '(' parameter_list ')' optional_return_type T_DOUBLE_ARROW expr
|
||||
| attributes T_FN optional_ref '(' parameter_list ')' optional_return_type T_DOUBLE_ARROW expr %prec T_THROW
|
||||
{ $$ = Expr\ArrowFunction[['static' => false, 'byRef' => $3, 'params' => $5, 'returnType' => $7, 'expr' => $9, 'attrGroups' => $1]]; }
|
||||
| attributes T_STATIC T_FN optional_ref '(' parameter_list ')' optional_return_type T_DOUBLE_ARROW expr
|
||||
| attributes T_STATIC T_FN optional_ref '(' parameter_list ')' optional_return_type T_DOUBLE_ARROW expr %prec T_THROW
|
||||
{ $$ = Expr\ArrowFunction[['static' => true, 'byRef' => $4, 'params' => $6, 'returnType' => $8, 'expr' => $10, 'attrGroups' => $1]]; }
|
||||
| attributes T_FUNCTION optional_ref '(' parameter_list ')' lexical_vars optional_return_type block_or_error
|
||||
{ $$ = Expr\Closure[['static' => false, 'byRef' => $3, 'params' => $5, 'uses' => $7, 'returnType' => $8, 'stmts' => $9, 'attrGroups' => $1]]; }
|
||||
@ -942,7 +995,7 @@ constant:
|
||||
;
|
||||
|
||||
class_constant:
|
||||
class_name_or_var T_PAAMAYIM_NEKUDOTAYIM identifier_ex
|
||||
class_name_or_var T_PAAMAYIM_NEKUDOTAYIM identifier_maybe_reserved
|
||||
{ $$ = Expr\ClassConstFetch[$1, $3]; }
|
||||
/* We interpret an isolated FOO:: as an unfinished class constant fetch. It could also be
|
||||
an unfinished static property fetch or unfinished scoped call. */
|
||||
@ -1007,7 +1060,7 @@ callable_expr:
|
||||
;
|
||||
|
||||
callable_variable:
|
||||
simple_variable { $$ = Expr\Variable[$1]; }
|
||||
simple_variable { $$ = $1; }
|
||||
| array_object_dereferencable '[' optional_expr ']' { $$ = Expr\ArrayDimFetch[$1, $3]; }
|
||||
| array_object_dereferencable '{' expr '}' { $$ = Expr\ArrayDimFetch[$1, $3]; }
|
||||
| function_call { $$ = $1; }
|
||||
@ -1032,15 +1085,15 @@ variable:
|
||||
;
|
||||
|
||||
simple_variable:
|
||||
T_VARIABLE { $$ = parseVar($1); }
|
||||
| '$' '{' expr '}' { $$ = $3; }
|
||||
plain_variable { $$ = $1; }
|
||||
| '$' '{' expr '}' { $$ = Expr\Variable[$3]; }
|
||||
| '$' simple_variable { $$ = Expr\Variable[$2]; }
|
||||
| '$' error { $$ = Expr\Error[]; $this->errorState = 2; }
|
||||
| '$' error { $$ = Expr\Variable[Expr\Error[]]; $this->errorState = 2; }
|
||||
;
|
||||
|
||||
static_member_prop_name:
|
||||
simple_variable
|
||||
{ $var = $1; $$ = \is_string($var) ? Node\VarLikeIdentifier[$var] : $var; }
|
||||
{ $var = $1->name; $$ = \is_string($var) ? Node\VarLikeIdentifier[$var] : $var; }
|
||||
;
|
||||
|
||||
static_member:
|
||||
@ -1049,7 +1102,7 @@ static_member:
|
||||
;
|
||||
|
||||
new_variable:
|
||||
simple_variable { $$ = Expr\Variable[$1]; }
|
||||
simple_variable { $$ = $1; }
|
||||
| new_variable '[' optional_expr ']' { $$ = Expr\ArrayDimFetch[$1, $3]; }
|
||||
| new_variable '{' expr '}' { $$ = Expr\ArrayDimFetch[$1, $3]; }
|
||||
| new_variable T_OBJECT_OPERATOR property_name { $$ = Expr\PropertyFetch[$1, $3]; }
|
||||
@ -1061,15 +1114,15 @@ new_variable:
|
||||
;
|
||||
|
||||
member_name:
|
||||
identifier_ex { $$ = $1; }
|
||||
identifier_maybe_reserved { $$ = $1; }
|
||||
| '{' expr '}' { $$ = $2; }
|
||||
| simple_variable { $$ = Expr\Variable[$1]; }
|
||||
| simple_variable { $$ = $1; }
|
||||
;
|
||||
|
||||
property_name:
|
||||
identifier { $$ = $1; }
|
||||
identifier_not_reserved { $$ = $1; }
|
||||
| '{' expr '}' { $$ = $2; }
|
||||
| simple_variable { $$ = Expr\Variable[$1]; }
|
||||
| simple_variable { $$ = $1; }
|
||||
| error { $$ = Expr\Error[]; $this->errorState = 2; }
|
||||
;
|
||||
|
||||
@ -1095,10 +1148,10 @@ inner_array_pair_list:
|
||||
|
||||
array_pair:
|
||||
expr { $$ = Expr\ArrayItem[$1, null, false]; }
|
||||
| '&' variable { $$ = Expr\ArrayItem[$2, null, true]; }
|
||||
| ampersand variable { $$ = Expr\ArrayItem[$2, null, true]; }
|
||||
| list_expr { $$ = Expr\ArrayItem[$1, null, false]; }
|
||||
| expr T_DOUBLE_ARROW expr { $$ = Expr\ArrayItem[$3, $1, false]; }
|
||||
| expr T_DOUBLE_ARROW '&' variable { $$ = Expr\ArrayItem[$4, $1, true]; }
|
||||
| expr T_DOUBLE_ARROW ampersand variable { $$ = Expr\ArrayItem[$4, $1, true]; }
|
||||
| expr T_DOUBLE_ARROW list_expr { $$ = Expr\ArrayItem[$3, $1, false]; }
|
||||
| T_ELLIPSIS expr { $$ = Expr\ArrayItem[$2, null, false, attributes(), true]; }
|
||||
| /* empty */ { $$ = null; }
|
||||
@ -1122,8 +1175,10 @@ encaps_str_varname:
|
||||
encaps_var:
|
||||
plain_variable { $$ = $1; }
|
||||
| plain_variable '[' encaps_var_offset ']' { $$ = Expr\ArrayDimFetch[$1, $3]; }
|
||||
| plain_variable T_OBJECT_OPERATOR identifier { $$ = Expr\PropertyFetch[$1, $3]; }
|
||||
| plain_variable T_NULLSAFE_OBJECT_OPERATOR identifier { $$ = Expr\NullsafePropertyFetch[$1, $3]; }
|
||||
| plain_variable T_OBJECT_OPERATOR identifier_not_reserved
|
||||
{ $$ = Expr\PropertyFetch[$1, $3]; }
|
||||
| plain_variable T_NULLSAFE_OBJECT_OPERATOR identifier_not_reserved
|
||||
{ $$ = Expr\NullsafePropertyFetch[$1, $3]; }
|
||||
| T_DOLLAR_OPEN_CURLY_BRACES expr '}' { $$ = Expr\Variable[$2]; }
|
||||
| T_DOLLAR_OPEN_CURLY_BRACES T_STRING_VARNAME '}' { $$ = Expr\Variable[$2]; }
|
||||
| T_DOLLAR_OPEN_CURLY_BRACES encaps_str_varname '[' expr ']' '}'
|
||||
|
192
grammar/phpyLang.php
Normal file
192
grammar/phpyLang.php
Normal file
@ -0,0 +1,192 @@
|
||||
<?php
|
||||
|
||||
///////////////////////////////
|
||||
/// Utility regex constants ///
|
||||
///////////////////////////////
|
||||
|
||||
const LIB = '(?(DEFINE)
|
||||
(?<singleQuotedString>\'[^\\\\\']*+(?:\\\\.[^\\\\\']*+)*+\')
|
||||
(?<doubleQuotedString>"[^\\\\"]*+(?:\\\\.[^\\\\"]*+)*+")
|
||||
(?<string>(?&singleQuotedString)|(?&doubleQuotedString))
|
||||
(?<comment>/\*[^*]*+(?:\*(?!/)[^*]*+)*+\*/)
|
||||
(?<code>\{[^\'"/{}]*+(?:(?:(?&string)|(?&comment)|(?&code)|/)[^\'"/{}]*+)*+})
|
||||
)';
|
||||
|
||||
const PARAMS = '\[(?<params>[^[\]]*+(?:\[(?¶ms)\][^[\]]*+)*+)\]';
|
||||
const ARGS = '\((?<args>[^()]*+(?:\((?&args)\)[^()]*+)*+)\)';
|
||||
|
||||
///////////////////////////////
|
||||
/// Preprocessing functions ///
|
||||
///////////////////////////////
|
||||
|
||||
function preprocessGrammar($code) {
|
||||
$code = resolveNodes($code);
|
||||
$code = resolveMacros($code);
|
||||
$code = resolveStackAccess($code);
|
||||
|
||||
return $code;
|
||||
}
|
||||
|
||||
function resolveNodes($code) {
|
||||
return preg_replace_callback(
|
||||
'~\b(?<name>[A-Z][a-zA-Z_\\\\]++)\s*' . PARAMS . '~',
|
||||
function($matches) {
|
||||
// recurse
|
||||
$matches['params'] = resolveNodes($matches['params']);
|
||||
|
||||
$params = magicSplit(
|
||||
'(?:' . PARAMS . '|' . ARGS . ')(*SKIP)(*FAIL)|,',
|
||||
$matches['params']
|
||||
);
|
||||
|
||||
$paramCode = '';
|
||||
foreach ($params as $param) {
|
||||
$paramCode .= $param . ', ';
|
||||
}
|
||||
|
||||
return 'new ' . $matches['name'] . '(' . $paramCode . 'attributes())';
|
||||
},
|
||||
$code
|
||||
);
|
||||
}
|
||||
|
||||
function resolveMacros($code) {
|
||||
return preg_replace_callback(
|
||||
'~\b(?<!::|->)(?!array\()(?<name>[a-z][A-Za-z]++)' . ARGS . '~',
|
||||
function($matches) {
|
||||
// recurse
|
||||
$matches['args'] = resolveMacros($matches['args']);
|
||||
|
||||
$name = $matches['name'];
|
||||
$args = magicSplit(
|
||||
'(?:' . PARAMS . '|' . ARGS . ')(*SKIP)(*FAIL)|,',
|
||||
$matches['args']
|
||||
);
|
||||
|
||||
if ('attributes' === $name) {
|
||||
assertArgs(0, $args, $name);
|
||||
return '$this->startAttributeStack[#1] + $this->endAttributes';
|
||||
}
|
||||
|
||||
if ('stackAttributes' === $name) {
|
||||
assertArgs(1, $args, $name);
|
||||
return '$this->startAttributeStack[' . $args[0] . ']'
|
||||
. ' + $this->endAttributeStack[' . $args[0] . ']';
|
||||
}
|
||||
|
||||
if ('init' === $name) {
|
||||
return '$$ = array(' . implode(', ', $args) . ')';
|
||||
}
|
||||
|
||||
if ('push' === $name) {
|
||||
assertArgs(2, $args, $name);
|
||||
|
||||
return $args[0] . '[] = ' . $args[1] . '; $$ = ' . $args[0];
|
||||
}
|
||||
|
||||
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] . '; }';
|
||||
}
|
||||
|
||||
if ('toArray' == $name) {
|
||||
assertArgs(1, $args, $name);
|
||||
|
||||
return 'is_array(' . $args[0] . ') ? ' . $args[0] . ' : array(' . $args[0] . ')';
|
||||
}
|
||||
|
||||
if ('parseVar' === $name) {
|
||||
assertArgs(1, $args, $name);
|
||||
|
||||
return 'substr(' . $args[0] . ', 1)';
|
||||
}
|
||||
|
||||
if ('parseEncapsed' === $name) {
|
||||
assertArgs(3, $args, $name);
|
||||
|
||||
return 'foreach (' . $args[0] . ' as $s) { if ($s instanceof Node\Scalar\EncapsedStringPart) {'
|
||||
. ' $s->value = Node\Scalar\String_::parseEscapeSequences($s->value, ' . $args[1] . ', ' . $args[2] . '); } }';
|
||||
}
|
||||
|
||||
if ('makeNop' === $name) {
|
||||
assertArgs(3, $args, $name);
|
||||
|
||||
return '$startAttributes = ' . $args[1] . ';'
|
||||
. ' if (isset($startAttributes[\'comments\']))'
|
||||
. ' { ' . $args[0] . ' = new Stmt\Nop($startAttributes + ' . $args[2] . '); }'
|
||||
. ' else { ' . $args[0] . ' = null; }';
|
||||
}
|
||||
|
||||
if ('makeZeroLengthNop' == $name) {
|
||||
assertArgs(2, $args, $name);
|
||||
|
||||
return '$startAttributes = ' . $args[1] . ';'
|
||||
. ' if (isset($startAttributes[\'comments\']))'
|
||||
. ' { ' . $args[0] . ' = new Stmt\Nop($this->createCommentNopAttributes($startAttributes[\'comments\'])); }'
|
||||
. ' else { ' . $args[0] . ' = null; }';
|
||||
}
|
||||
|
||||
if ('strKind' === $name) {
|
||||
assertArgs(1, $args, $name);
|
||||
|
||||
return '(' . $args[0] . '[0] === "\'" || (' . $args[0] . '[1] === "\'" && '
|
||||
. '(' . $args[0] . '[0] === \'b\' || ' . $args[0] . '[0] === \'B\')) '
|
||||
. '? Scalar\String_::KIND_SINGLE_QUOTED : Scalar\String_::KIND_DOUBLE_QUOTED)';
|
||||
}
|
||||
|
||||
if ('prependLeadingComments' === $name) {
|
||||
assertArgs(1, $args, $name);
|
||||
|
||||
return '$attrs = $this->startAttributeStack[#1]; $stmts = ' . $args[0] . '; '
|
||||
. 'if (!empty($attrs[\'comments\'])) {'
|
||||
. '$stmts[0]->setAttribute(\'comments\', '
|
||||
. 'array_merge($attrs[\'comments\'], $stmts[0]->getAttribute(\'comments\', []))); }';
|
||||
}
|
||||
|
||||
return $matches[0];
|
||||
},
|
||||
$code
|
||||
);
|
||||
}
|
||||
|
||||
function assertArgs($num, $args, $name) {
|
||||
if ($num != count($args)) {
|
||||
die('Wrong argument count for ' . $name . '().');
|
||||
}
|
||||
}
|
||||
|
||||
function resolveStackAccess($code) {
|
||||
$code = preg_replace('/\$\d+/', '$this->semStack[$0]', $code);
|
||||
$code = preg_replace('/#(\d+)/', '$$1', $code);
|
||||
return $code;
|
||||
}
|
||||
|
||||
function removeTrailingWhitespace($code) {
|
||||
$lines = explode("\n", $code);
|
||||
$lines = array_map('rtrim', $lines);
|
||||
return implode("\n", $lines);
|
||||
}
|
||||
|
||||
//////////////////////////////
|
||||
/// Regex helper functions ///
|
||||
//////////////////////////////
|
||||
|
||||
function regex($regex) {
|
||||
return '~' . LIB . '(?:' . str_replace('~', '\~', $regex) . ')~';
|
||||
}
|
||||
|
||||
function magicSplit($regex, $string) {
|
||||
$pieces = preg_split(regex('(?:(?&string)|(?&comment)|(?&code))(*SKIP)(*FAIL)|' . $regex), $string);
|
||||
|
||||
foreach ($pieces as &$piece) {
|
||||
$piece = trim($piece);
|
||||
}
|
||||
|
||||
if ($pieces === ['']) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return $pieces;
|
||||
}
|
@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
require __DIR__ . '/phpyLang.php';
|
||||
|
||||
$grammarFileToName = [
|
||||
__DIR__ . '/php5.y' => 'Php5',
|
||||
__DIR__ . '/php7.y' => 'Php7',
|
||||
@ -23,21 +25,6 @@ $options = array_flip($argv);
|
||||
$optionDebug = isset($options['--debug']);
|
||||
$optionKeepTmpGrammar = isset($options['--keep-tmp-grammar']);
|
||||
|
||||
///////////////////////////////
|
||||
/// Utility regex constants ///
|
||||
///////////////////////////////
|
||||
|
||||
const LIB = '(?(DEFINE)
|
||||
(?<singleQuotedString>\'[^\\\\\']*+(?:\\\\.[^\\\\\']*+)*+\')
|
||||
(?<doubleQuotedString>"[^\\\\"]*+(?:\\\\.[^\\\\"]*+)*+")
|
||||
(?<string>(?&singleQuotedString)|(?&doubleQuotedString))
|
||||
(?<comment>/\*[^*]*+(?:\*(?!/)[^*]*+)*+\*/)
|
||||
(?<code>\{[^\'"/{}]*+(?:(?:(?&string)|(?&comment)|(?&code)|/)[^\'"/{}]*+)*+})
|
||||
)';
|
||||
|
||||
const PARAMS = '\[(?<params>[^[\]]*+(?:\[(?¶ms)\][^[\]]*+)*+)\]';
|
||||
const ARGS = '\((?<args>[^()]*+(?:\((?&args)\)[^()]*+)*+)\)';
|
||||
|
||||
///////////////////
|
||||
/// Main script ///
|
||||
///////////////////
|
||||
@ -49,10 +36,7 @@ foreach ($grammarFileToName as $grammarFile => $name) {
|
||||
|
||||
$grammarCode = file_get_contents($grammarFile);
|
||||
$grammarCode = str_replace('%tokens', $tokens, $grammarCode);
|
||||
|
||||
$grammarCode = resolveNodes($grammarCode);
|
||||
$grammarCode = resolveMacros($grammarCode);
|
||||
$grammarCode = resolveStackAccess($grammarCode);
|
||||
$grammarCode = preprocessGrammar($grammarCode);
|
||||
|
||||
file_put_contents($tmpGrammarFile, $grammarCode);
|
||||
|
||||
@ -77,151 +61,9 @@ foreach ($grammarFileToName as $grammarFile => $name) {
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////
|
||||
/// Preprocessing functions ///
|
||||
///////////////////////////////
|
||||
|
||||
function resolveNodes($code) {
|
||||
return preg_replace_callback(
|
||||
'~\b(?<name>[A-Z][a-zA-Z_\\\\]++)\s*' . PARAMS . '~',
|
||||
function($matches) {
|
||||
// recurse
|
||||
$matches['params'] = resolveNodes($matches['params']);
|
||||
|
||||
$params = magicSplit(
|
||||
'(?:' . PARAMS . '|' . ARGS . ')(*SKIP)(*FAIL)|,',
|
||||
$matches['params']
|
||||
);
|
||||
|
||||
$paramCode = '';
|
||||
foreach ($params as $param) {
|
||||
$paramCode .= $param . ', ';
|
||||
}
|
||||
|
||||
return 'new ' . $matches['name'] . '(' . $paramCode . 'attributes())';
|
||||
},
|
||||
$code
|
||||
);
|
||||
}
|
||||
|
||||
function resolveMacros($code) {
|
||||
return preg_replace_callback(
|
||||
'~\b(?<!::|->)(?!array\()(?<name>[a-z][A-Za-z]++)' . ARGS . '~',
|
||||
function($matches) {
|
||||
// recurse
|
||||
$matches['args'] = resolveMacros($matches['args']);
|
||||
|
||||
$name = $matches['name'];
|
||||
$args = magicSplit(
|
||||
'(?:' . PARAMS . '|' . ARGS . ')(*SKIP)(*FAIL)|,',
|
||||
$matches['args']
|
||||
);
|
||||
|
||||
if ('attributes' === $name) {
|
||||
assertArgs(0, $args, $name);
|
||||
return '$this->startAttributeStack[#1] + $this->endAttributes';
|
||||
}
|
||||
|
||||
if ('stackAttributes' === $name) {
|
||||
assertArgs(1, $args, $name);
|
||||
return '$this->startAttributeStack[' . $args[0] . ']'
|
||||
. ' + $this->endAttributeStack[' . $args[0] . ']';
|
||||
}
|
||||
|
||||
if ('init' === $name) {
|
||||
return '$$ = array(' . implode(', ', $args) . ')';
|
||||
}
|
||||
|
||||
if ('push' === $name) {
|
||||
assertArgs(2, $args, $name);
|
||||
|
||||
return $args[0] . '[] = ' . $args[1] . '; $$ = ' . $args[0];
|
||||
}
|
||||
|
||||
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] . '; }';
|
||||
}
|
||||
|
||||
if ('toArray' == $name) {
|
||||
assertArgs(1, $args, $name);
|
||||
|
||||
return 'is_array(' . $args[0] . ') ? ' . $args[0] . ' : array(' . $args[0] . ')';
|
||||
}
|
||||
|
||||
if ('parseVar' === $name) {
|
||||
assertArgs(1, $args, $name);
|
||||
|
||||
return 'substr(' . $args[0] . ', 1)';
|
||||
}
|
||||
|
||||
if ('parseEncapsed' === $name) {
|
||||
assertArgs(3, $args, $name);
|
||||
|
||||
return 'foreach (' . $args[0] . ' as $s) { if ($s instanceof Node\Scalar\EncapsedStringPart) {'
|
||||
. ' $s->value = Node\Scalar\String_::parseEscapeSequences($s->value, ' . $args[1] . ', ' . $args[2] . '); } }';
|
||||
}
|
||||
|
||||
if ('makeNop' === $name) {
|
||||
assertArgs(3, $args, $name);
|
||||
|
||||
return '$startAttributes = ' . $args[1] . ';'
|
||||
. ' if (isset($startAttributes[\'comments\']))'
|
||||
. ' { ' . $args[0] . ' = new Stmt\Nop($startAttributes + ' . $args[2] . '); }'
|
||||
. ' else { ' . $args[0] . ' = null; }';
|
||||
}
|
||||
|
||||
if ('makeZeroLengthNop' == $name) {
|
||||
assertArgs(2, $args, $name);
|
||||
|
||||
return '$startAttributes = ' . $args[1] . ';'
|
||||
. ' if (isset($startAttributes[\'comments\']))'
|
||||
. ' { ' . $args[0] . ' = new Stmt\Nop($this->createCommentNopAttributes($startAttributes[\'comments\'])); }'
|
||||
. ' else { ' . $args[0] . ' = null; }';
|
||||
}
|
||||
|
||||
if ('strKind' === $name) {
|
||||
assertArgs(1, $args, $name);
|
||||
|
||||
return '(' . $args[0] . '[0] === "\'" || (' . $args[0] . '[1] === "\'" && '
|
||||
. '(' . $args[0] . '[0] === \'b\' || ' . $args[0] . '[0] === \'B\')) '
|
||||
. '? Scalar\String_::KIND_SINGLE_QUOTED : Scalar\String_::KIND_DOUBLE_QUOTED)';
|
||||
}
|
||||
|
||||
if ('prependLeadingComments' === $name) {
|
||||
assertArgs(1, $args, $name);
|
||||
|
||||
return '$attrs = $this->startAttributeStack[#1]; $stmts = ' . $args[0] . '; '
|
||||
. 'if (!empty($attrs[\'comments\'])) {'
|
||||
. '$stmts[0]->setAttribute(\'comments\', '
|
||||
. 'array_merge($attrs[\'comments\'], $stmts[0]->getAttribute(\'comments\', []))); }';
|
||||
}
|
||||
|
||||
return $matches[0];
|
||||
},
|
||||
$code
|
||||
);
|
||||
}
|
||||
|
||||
function assertArgs($num, $args, $name) {
|
||||
if ($num != count($args)) {
|
||||
die('Wrong argument count for ' . $name . '().');
|
||||
}
|
||||
}
|
||||
|
||||
function resolveStackAccess($code) {
|
||||
$code = preg_replace('/\$\d+/', '$this->semStack[$0]', $code);
|
||||
$code = preg_replace('/#(\d+)/', '$$1', $code);
|
||||
return $code;
|
||||
}
|
||||
|
||||
function removeTrailingWhitespace($code) {
|
||||
$lines = explode("\n", $code);
|
||||
$lines = array_map('rtrim', $lines);
|
||||
return implode("\n", $lines);
|
||||
}
|
||||
////////////////////////////////
|
||||
/// Utility helper functions ///
|
||||
////////////////////////////////
|
||||
|
||||
function ensureDirExists($dir) {
|
||||
if (!is_dir($dir)) {
|
||||
@ -237,25 +79,3 @@ function execCmd($cmd) {
|
||||
}
|
||||
return $output;
|
||||
}
|
||||
|
||||
//////////////////////////////
|
||||
/// Regex helper functions ///
|
||||
//////////////////////////////
|
||||
|
||||
function regex($regex) {
|
||||
return '~' . LIB . '(?:' . str_replace('~', '\~', $regex) . ')~';
|
||||
}
|
||||
|
||||
function magicSplit($regex, $string) {
|
||||
$pieces = preg_split(regex('(?:(?&string)|(?&comment)|(?&code))(*SKIP)(*FAIL)|' . $regex), $string);
|
||||
|
||||
foreach ($pieces as &$piece) {
|
||||
$piece = trim($piece);
|
||||
}
|
||||
|
||||
if ($pieces === ['']) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return $pieces;
|
||||
}
|
||||
|
@ -18,7 +18,7 @@
|
||||
%left T_BOOLEAN_AND
|
||||
%left '|'
|
||||
%left '^'
|
||||
%left '&'
|
||||
%left T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG
|
||||
%nonassoc T_IS_EQUAL T_IS_NOT_EQUAL T_IS_IDENTICAL T_IS_NOT_IDENTICAL T_SPACESHIP
|
||||
%nonassoc '<' T_IS_SMALLER_OR_EQUAL '>' T_IS_GREATER_OR_EQUAL
|
||||
%left T_SL T_SR
|
||||
@ -74,7 +74,7 @@
|
||||
%token T_USE
|
||||
%token T_INSTEADOF
|
||||
%token T_GLOBAL
|
||||
%right T_STATIC T_ABSTRACT T_FINAL T_PRIVATE T_PROTECTED T_PUBLIC
|
||||
%right T_STATIC T_ABSTRACT T_FINAL T_PRIVATE T_PROTECTED T_PUBLIC T_READONLY
|
||||
%token T_VAR
|
||||
%token T_UNSET
|
||||
%token T_ISSET
|
||||
@ -83,6 +83,7 @@
|
||||
%token T_CLASS
|
||||
%token T_TRAIT
|
||||
%token T_INTERFACE
|
||||
%token T_ENUM
|
||||
%token T_EXTENDS
|
||||
%token T_IMPLEMENTS
|
||||
%token T_OBJECT_OPERATOR
|
||||
@ -110,4 +111,5 @@
|
||||
%token T_NAME_FULLY_QUALIFIED
|
||||
%token T_NAME_QUALIFIED
|
||||
%token T_NAME_RELATIVE
|
||||
%token T_ATTRIBUTE
|
||||
%token T_ATTRIBUTE
|
||||
%token T_ENUM
|
||||
|
132
lib/PhpParser/Builder/ClassConst.php
Normal file
132
lib/PhpParser/Builder/ClassConst.php
Normal file
@ -0,0 +1,132 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpParser\Builder;
|
||||
|
||||
use PhpParser;
|
||||
use PhpParser\BuilderHelpers;
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Const_;
|
||||
use PhpParser\Node\Identifier;
|
||||
use PhpParser\Node\Stmt;
|
||||
|
||||
class ClassConst implements PhpParser\Builder
|
||||
{
|
||||
protected $flags = 0;
|
||||
protected $attributes = [];
|
||||
protected $constants = [];
|
||||
|
||||
/** @var Node\AttributeGroup[] */
|
||||
protected $attributeGroups = [];
|
||||
|
||||
/**
|
||||
* Creates a class constant builder
|
||||
*
|
||||
* @param string|Identifier $name Name
|
||||
* @param Node\Expr|bool|null|int|float|string|array $value Value
|
||||
*/
|
||||
public function __construct($name, $value) {
|
||||
$this->constants = [new Const_($name, BuilderHelpers::normalizeValue($value))];
|
||||
}
|
||||
|
||||
/**
|
||||
* Add another constant to const group
|
||||
*
|
||||
* @param string|Identifier $name Name
|
||||
* @param Node\Expr|bool|null|int|float|string|array $value Value
|
||||
*
|
||||
* @return $this The builder instance (for fluid interface)
|
||||
*/
|
||||
public function addConst($name, $value) {
|
||||
$this->constants[] = new Const_($name, BuilderHelpers::normalizeValue($value));
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes the constant public.
|
||||
*
|
||||
* @return $this The builder instance (for fluid interface)
|
||||
*/
|
||||
public function makePublic() {
|
||||
$this->flags = BuilderHelpers::addModifier($this->flags, Stmt\Class_::MODIFIER_PUBLIC);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes the constant protected.
|
||||
*
|
||||
* @return $this The builder instance (for fluid interface)
|
||||
*/
|
||||
public function makeProtected() {
|
||||
$this->flags = BuilderHelpers::addModifier($this->flags, Stmt\Class_::MODIFIER_PROTECTED);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes the constant private.
|
||||
*
|
||||
* @return $this The builder instance (for fluid interface)
|
||||
*/
|
||||
public function makePrivate() {
|
||||
$this->flags = BuilderHelpers::addModifier($this->flags, Stmt\Class_::MODIFIER_PRIVATE);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes the constant final.
|
||||
*
|
||||
* @return $this The builder instance (for fluid interface)
|
||||
*/
|
||||
public function makeFinal() {
|
||||
$this->flags = BuilderHelpers::addModifier($this->flags, Stmt\Class_::MODIFIER_FINAL);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets doc comment for the constant.
|
||||
*
|
||||
* @param PhpParser\Comment\Doc|string $docComment Doc comment to set
|
||||
*
|
||||
* @return $this The builder instance (for fluid interface)
|
||||
*/
|
||||
public function setDocComment($docComment) {
|
||||
$this->attributes = [
|
||||
'comments' => [BuilderHelpers::normalizeDocComment($docComment)]
|
||||
];
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an attribute group.
|
||||
*
|
||||
* @param Node\Attribute|Node\AttributeGroup $attribute
|
||||
*
|
||||
* @return $this The builder instance (for fluid interface)
|
||||
*/
|
||||
public function addAttribute($attribute) {
|
||||
$this->attributeGroups[] = BuilderHelpers::normalizeAttribute($attribute);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the built class node.
|
||||
*
|
||||
* @return Stmt\ClassConst The built constant node
|
||||
*/
|
||||
public function getNode(): PhpParser\Node {
|
||||
return new Stmt\ClassConst(
|
||||
$this->constants,
|
||||
$this->flags,
|
||||
$this->attributes,
|
||||
$this->attributeGroups
|
||||
);
|
||||
}
|
||||
}
|
@ -4,6 +4,7 @@ namespace PhpParser\Builder;
|
||||
|
||||
use PhpParser;
|
||||
use PhpParser\BuilderHelpers;
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Name;
|
||||
use PhpParser\Node\Stmt;
|
||||
|
||||
@ -20,6 +21,9 @@ class Class_ extends Declaration
|
||||
protected $properties = [];
|
||||
protected $methods = [];
|
||||
|
||||
/** @var Node\AttributeGroup[] */
|
||||
protected $attributeGroups = [];
|
||||
|
||||
/**
|
||||
* Creates a class builder.
|
||||
*
|
||||
@ -106,6 +110,19 @@ class Class_ extends Declaration
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an attribute group.
|
||||
*
|
||||
* @param Node\Attribute|Node\AttributeGroup $attribute
|
||||
*
|
||||
* @return $this The builder instance (for fluid interface)
|
||||
*/
|
||||
public function addAttribute($attribute) {
|
||||
$this->attributeGroups[] = BuilderHelpers::normalizeAttribute($attribute);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the built class node.
|
||||
*
|
||||
@ -117,6 +134,7 @@ class Class_ extends Declaration
|
||||
'extends' => $this->extends,
|
||||
'implements' => $this->implements,
|
||||
'stmts' => array_merge($this->uses, $this->constants, $this->properties, $this->methods),
|
||||
'attrGroups' => $this->attributeGroups,
|
||||
], $this->attributes);
|
||||
}
|
||||
}
|
||||
|
85
lib/PhpParser/Builder/EnumCase.php
Normal file
85
lib/PhpParser/Builder/EnumCase.php
Normal file
@ -0,0 +1,85 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpParser\Builder;
|
||||
|
||||
use PhpParser;
|
||||
use PhpParser\BuilderHelpers;
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Identifier;
|
||||
use PhpParser\Node\Stmt;
|
||||
|
||||
class EnumCase implements PhpParser\Builder
|
||||
{
|
||||
protected $name;
|
||||
protected $value = null;
|
||||
protected $attributes = [];
|
||||
|
||||
/** @var Node\AttributeGroup[] */
|
||||
protected $attributeGroups = [];
|
||||
|
||||
/**
|
||||
* Creates an enum case builder.
|
||||
*
|
||||
* @param string|Identifier $name Name
|
||||
*/
|
||||
public function __construct($name) {
|
||||
$this->name = $name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the value.
|
||||
*
|
||||
* @param Node\Expr|string|int $value
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setValue($value) {
|
||||
$this->value = BuilderHelpers::normalizeValue($value);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets doc comment for the constant.
|
||||
*
|
||||
* @param PhpParser\Comment\Doc|string $docComment Doc comment to set
|
||||
*
|
||||
* @return $this The builder instance (for fluid interface)
|
||||
*/
|
||||
public function setDocComment($docComment) {
|
||||
$this->attributes = [
|
||||
'comments' => [BuilderHelpers::normalizeDocComment($docComment)]
|
||||
];
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an attribute group.
|
||||
*
|
||||
* @param Node\Attribute|Node\AttributeGroup $attribute
|
||||
*
|
||||
* @return $this The builder instance (for fluid interface)
|
||||
*/
|
||||
public function addAttribute($attribute) {
|
||||
$this->attributeGroups[] = BuilderHelpers::normalizeAttribute($attribute);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the built enum case node.
|
||||
*
|
||||
* @return Stmt\EnumCase The built constant node
|
||||
*/
|
||||
public function getNode(): PhpParser\Node {
|
||||
return new Stmt\EnumCase(
|
||||
$this->name,
|
||||
$this->value,
|
||||
$this->attributes,
|
||||
$this->attributeGroups
|
||||
);
|
||||
}
|
||||
}
|
117
lib/PhpParser/Builder/Enum_.php
Normal file
117
lib/PhpParser/Builder/Enum_.php
Normal file
@ -0,0 +1,117 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser\Builder;
|
||||
|
||||
use PhpParser;
|
||||
use PhpParser\BuilderHelpers;
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Identifier;
|
||||
use PhpParser\Node\Name;
|
||||
use PhpParser\Node\Stmt;
|
||||
|
||||
class Enum_ extends Declaration
|
||||
{
|
||||
protected $name;
|
||||
protected $scalarType = null;
|
||||
|
||||
protected $implements = [];
|
||||
|
||||
protected $uses = [];
|
||||
protected $enumCases = [];
|
||||
protected $constants = [];
|
||||
protected $methods = [];
|
||||
|
||||
/** @var Node\AttributeGroup[] */
|
||||
protected $attributeGroups = [];
|
||||
|
||||
/**
|
||||
* Creates an enum builder.
|
||||
*
|
||||
* @param string $name Name of the enum
|
||||
*/
|
||||
public function __construct(string $name) {
|
||||
$this->name = $name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the scalar type.
|
||||
*
|
||||
* @param string|Identifier $type
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setScalarType($scalarType) {
|
||||
$this->scalarType = BuilderHelpers::normalizeType($scalarType);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements one or more interfaces.
|
||||
*
|
||||
* @param Name|string ...$interfaces Names of interfaces to implement
|
||||
*
|
||||
* @return $this The builder instance (for fluid interface)
|
||||
*/
|
||||
public function implement(...$interfaces) {
|
||||
foreach ($interfaces as $interface) {
|
||||
$this->implements[] = BuilderHelpers::normalizeName($interface);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a statement.
|
||||
*
|
||||
* @param Stmt|PhpParser\Builder $stmt The statement to add
|
||||
*
|
||||
* @return $this The builder instance (for fluid interface)
|
||||
*/
|
||||
public function addStmt($stmt) {
|
||||
$stmt = BuilderHelpers::normalizeNode($stmt);
|
||||
|
||||
$targets = [
|
||||
Stmt\TraitUse::class => &$this->uses,
|
||||
Stmt\EnumCase::class => &$this->enumCases,
|
||||
Stmt\ClassConst::class => &$this->constants,
|
||||
Stmt\ClassMethod::class => &$this->methods,
|
||||
];
|
||||
|
||||
$class = \get_class($stmt);
|
||||
if (!isset($targets[$class])) {
|
||||
throw new \LogicException(sprintf('Unexpected node of type "%s"', $stmt->getType()));
|
||||
}
|
||||
|
||||
$targets[$class][] = $stmt;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an attribute group.
|
||||
*
|
||||
* @param Node\Attribute|Node\AttributeGroup $attribute
|
||||
*
|
||||
* @return $this The builder instance (for fluid interface)
|
||||
*/
|
||||
public function addAttribute($attribute) {
|
||||
$this->attributeGroups[] = BuilderHelpers::normalizeAttribute($attribute);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the built class node.
|
||||
*
|
||||
* @return Stmt\Enum_ The built enum node
|
||||
*/
|
||||
public function getNode() : PhpParser\Node {
|
||||
return new Stmt\Enum_($this->name, [
|
||||
'scalarType' => $this->scalarType,
|
||||
'implements' => $this->implements,
|
||||
'stmts' => array_merge($this->uses, $this->enumCases, $this->constants, $this->methods),
|
||||
'attrGroups' => $this->attributeGroups,
|
||||
], $this->attributes);
|
||||
}
|
||||
}
|
@ -61,8 +61,7 @@ abstract class FunctionLike extends Declaration
|
||||
/**
|
||||
* Sets the return type for PHP 7.
|
||||
*
|
||||
* @param string|Node\Name|Node\NullableType $type One of array, callable, string, int, float,
|
||||
* bool, iterable, or a class/interface name.
|
||||
* @param string|Node\Name|Node\Identifier|Node\ComplexType $type
|
||||
*
|
||||
* @return $this The builder instance (for fluid interface)
|
||||
*/
|
||||
|
@ -12,6 +12,9 @@ class Function_ extends FunctionLike
|
||||
protected $name;
|
||||
protected $stmts = [];
|
||||
|
||||
/** @var Node\AttributeGroup[] */
|
||||
protected $attributeGroups = [];
|
||||
|
||||
/**
|
||||
* Creates a function builder.
|
||||
*
|
||||
@ -34,6 +37,19 @@ class Function_ extends FunctionLike
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an attribute group.
|
||||
*
|
||||
* @param Node\Attribute|Node\AttributeGroup $attribute
|
||||
*
|
||||
* @return $this The builder instance (for fluid interface)
|
||||
*/
|
||||
public function addAttribute($attribute) {
|
||||
$this->attributeGroups[] = BuilderHelpers::normalizeAttribute($attribute);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the built function node.
|
||||
*
|
||||
@ -45,6 +61,7 @@ class Function_ extends FunctionLike
|
||||
'params' => $this->params,
|
||||
'returnType' => $this->returnType,
|
||||
'stmts' => $this->stmts,
|
||||
'attrGroups' => $this->attributeGroups,
|
||||
], $this->attributes);
|
||||
}
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ namespace PhpParser\Builder;
|
||||
|
||||
use PhpParser;
|
||||
use PhpParser\BuilderHelpers;
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Name;
|
||||
use PhpParser\Node\Stmt;
|
||||
|
||||
@ -14,6 +15,9 @@ class Interface_ extends Declaration
|
||||
protected $constants = [];
|
||||
protected $methods = [];
|
||||
|
||||
/** @var Node\AttributeGroup[] */
|
||||
protected $attributeGroups = [];
|
||||
|
||||
/**
|
||||
* Creates an interface builder.
|
||||
*
|
||||
@ -61,6 +65,19 @@ class Interface_ extends Declaration
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an attribute group.
|
||||
*
|
||||
* @param Node\Attribute|Node\AttributeGroup $attribute
|
||||
*
|
||||
* @return $this The builder instance (for fluid interface)
|
||||
*/
|
||||
public function addAttribute($attribute) {
|
||||
$this->attributeGroups[] = BuilderHelpers::normalizeAttribute($attribute);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the built interface node.
|
||||
*
|
||||
@ -70,6 +87,7 @@ class Interface_ extends Declaration
|
||||
return new Stmt\Interface_($this->name, [
|
||||
'extends' => $this->extends,
|
||||
'stmts' => array_merge($this->constants, $this->methods),
|
||||
'attrGroups' => $this->attributeGroups,
|
||||
], $this->attributes);
|
||||
}
|
||||
}
|
||||
|
@ -15,6 +15,9 @@ class Method extends FunctionLike
|
||||
/** @var array|null */
|
||||
protected $stmts = [];
|
||||
|
||||
/** @var Node\AttributeGroup[] */
|
||||
protected $attributeGroups = [];
|
||||
|
||||
/**
|
||||
* Creates a method builder.
|
||||
*
|
||||
@ -112,6 +115,19 @@ class Method extends FunctionLike
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an attribute group.
|
||||
*
|
||||
* @param Node\Attribute|Node\AttributeGroup $attribute
|
||||
*
|
||||
* @return $this The builder instance (for fluid interface)
|
||||
*/
|
||||
public function addAttribute($attribute) {
|
||||
$this->attributeGroups[] = BuilderHelpers::normalizeAttribute($attribute);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the built method node.
|
||||
*
|
||||
@ -124,6 +140,7 @@ class Method extends FunctionLike
|
||||
'params' => $this->params,
|
||||
'returnType' => $this->returnType,
|
||||
'stmts' => $this->stmts,
|
||||
'attrGroups' => $this->attributeGroups,
|
||||
], $this->attributes);
|
||||
}
|
||||
}
|
||||
|
@ -37,7 +37,7 @@ class Namespace_ extends Declaration
|
||||
/**
|
||||
* Returns the built node.
|
||||
*
|
||||
* @return Node The built node
|
||||
* @return Stmt\Namespace_ The built node
|
||||
*/
|
||||
public function getNode() : Node {
|
||||
return new Stmt\Namespace_($this->name, $this->stmts, $this->attributes);
|
||||
|
@ -19,6 +19,9 @@ class Param implements PhpParser\Builder
|
||||
|
||||
protected $variadic = false;
|
||||
|
||||
/** @var Node\AttributeGroup[] */
|
||||
protected $attributeGroups = [];
|
||||
|
||||
/**
|
||||
* Creates a parameter builder.
|
||||
*
|
||||
@ -44,7 +47,7 @@ class Param implements PhpParser\Builder
|
||||
/**
|
||||
* Sets type for the parameter.
|
||||
*
|
||||
* @param string|Node\Name|Node\NullableType|Node\UnionType $type Parameter type
|
||||
* @param string|Node\Name|Node\Identifier|Node\ComplexType $type Parameter type
|
||||
*
|
||||
* @return $this The builder instance (for fluid interface)
|
||||
*/
|
||||
@ -60,7 +63,7 @@ class Param implements PhpParser\Builder
|
||||
/**
|
||||
* Sets type for the parameter.
|
||||
*
|
||||
* @param string|Node\Name|Node\NullableType|Node\UnionType $type Parameter type
|
||||
* @param string|Node\Name|Node\Identifier|Node\ComplexType $type Parameter type
|
||||
*
|
||||
* @return $this The builder instance (for fluid interface)
|
||||
*
|
||||
@ -92,6 +95,19 @@ class Param implements PhpParser\Builder
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an attribute group.
|
||||
*
|
||||
* @param Node\Attribute|Node\AttributeGroup $attribute
|
||||
*
|
||||
* @return $this The builder instance (for fluid interface)
|
||||
*/
|
||||
public function addAttribute($attribute) {
|
||||
$this->attributeGroups[] = BuilderHelpers::normalizeAttribute($attribute);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the built parameter node.
|
||||
*
|
||||
@ -100,7 +116,7 @@ class Param implements PhpParser\Builder
|
||||
public function getNode() : Node {
|
||||
return new Node\Param(
|
||||
new Node\Expr\Variable($this->name),
|
||||
$this->default, $this->type, $this->byRef, $this->variadic
|
||||
$this->default, $this->type, $this->byRef, $this->variadic, [], 0, $this->attributeGroups
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -4,10 +4,11 @@ namespace PhpParser\Builder;
|
||||
|
||||
use PhpParser;
|
||||
use PhpParser\BuilderHelpers;
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Identifier;
|
||||
use PhpParser\Node\Name;
|
||||
use PhpParser\Node\NullableType;
|
||||
use PhpParser\Node\Stmt;
|
||||
use PhpParser\Node\ComplexType;
|
||||
|
||||
class Property implements PhpParser\Builder
|
||||
{
|
||||
@ -20,6 +21,9 @@ class Property implements PhpParser\Builder
|
||||
/** @var null|Identifier|Name|NullableType */
|
||||
protected $type;
|
||||
|
||||
/** @var Node\AttributeGroup[] */
|
||||
protected $attributeGroups = [];
|
||||
|
||||
/**
|
||||
* Creates a property builder.
|
||||
*
|
||||
@ -73,6 +77,17 @@ class Property implements PhpParser\Builder
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes the property readonly.
|
||||
*
|
||||
* @return $this The builder instance (for fluid interface)
|
||||
*/
|
||||
public function makeReadonly() {
|
||||
$this->flags = BuilderHelpers::addModifier($this->flags, Stmt\Class_::MODIFIER_READONLY);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets default value for the property.
|
||||
*
|
||||
@ -104,7 +119,7 @@ class Property implements PhpParser\Builder
|
||||
/**
|
||||
* Sets the property type for PHP 7.4+.
|
||||
*
|
||||
* @param string|Name|NullableType|Identifier $type
|
||||
* @param string|Name|Identifier|ComplexType $type
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
@ -114,6 +129,19 @@ class Property implements PhpParser\Builder
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an attribute group.
|
||||
*
|
||||
* @param Node\Attribute|Node\AttributeGroup $attribute
|
||||
*
|
||||
* @return $this The builder instance (for fluid interface)
|
||||
*/
|
||||
public function addAttribute($attribute) {
|
||||
$this->attributeGroups[] = BuilderHelpers::normalizeAttribute($attribute);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the built class node.
|
||||
*
|
||||
@ -126,7 +154,8 @@ class Property implements PhpParser\Builder
|
||||
new Stmt\PropertyProperty($this->name, $this->default)
|
||||
],
|
||||
$this->attributes,
|
||||
$this->type
|
||||
$this->type,
|
||||
$this->attributeGroups
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ namespace PhpParser\Builder;
|
||||
|
||||
use PhpParser;
|
||||
use PhpParser\BuilderHelpers;
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Stmt;
|
||||
|
||||
class Trait_ extends Declaration
|
||||
@ -13,6 +14,9 @@ class Trait_ extends Declaration
|
||||
protected $properties = [];
|
||||
protected $methods = [];
|
||||
|
||||
/** @var Node\AttributeGroup[] */
|
||||
protected $attributeGroups = [];
|
||||
|
||||
/**
|
||||
* Creates an interface builder.
|
||||
*
|
||||
@ -45,6 +49,19 @@ class Trait_ extends Declaration
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an attribute group.
|
||||
*
|
||||
* @param Node\Attribute|Node\AttributeGroup $attribute
|
||||
*
|
||||
* @return $this The builder instance (for fluid interface)
|
||||
*/
|
||||
public function addAttribute($attribute) {
|
||||
$this->attributeGroups[] = BuilderHelpers::normalizeAttribute($attribute);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the built trait node.
|
||||
*
|
||||
@ -53,7 +70,8 @@ class Trait_ extends Declaration
|
||||
public function getNode() : PhpParser\Node {
|
||||
return new Stmt\Trait_(
|
||||
$this->name, [
|
||||
'stmts' => array_merge($this->uses, $this->properties, $this->methods)
|
||||
'stmts' => array_merge($this->uses, $this->properties, $this->methods),
|
||||
'attrGroups' => $this->attributeGroups,
|
||||
], $this->attributes
|
||||
);
|
||||
}
|
||||
|
@ -39,7 +39,7 @@ class Use_ implements Builder
|
||||
/**
|
||||
* Returns the built node.
|
||||
*
|
||||
* @return Node The built node
|
||||
* @return Stmt\Use_ The built node
|
||||
*/
|
||||
public function getNode() : Node {
|
||||
return new Stmt\Use_([
|
||||
|
@ -12,6 +12,21 @@ use PhpParser\Node\Stmt\Use_;
|
||||
|
||||
class BuilderFactory
|
||||
{
|
||||
/**
|
||||
* Creates an attribute node.
|
||||
*
|
||||
* @param string|Name $name Name of the attribute
|
||||
* @param array $args Attribute named arguments
|
||||
*
|
||||
* @return Node\Attribute
|
||||
*/
|
||||
public function attribute($name, array $args = []) : Node\Attribute {
|
||||
return new Node\Attribute(
|
||||
BuilderHelpers::normalizeName($name),
|
||||
$this->args($args)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a namespace builder.
|
||||
*
|
||||
@ -56,6 +71,17 @@ class BuilderFactory
|
||||
return new Builder\Trait_($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an enum builder.
|
||||
*
|
||||
* @param string $name Name of the enum
|
||||
*
|
||||
* @return Builder\Enum_ The created enum builder
|
||||
*/
|
||||
public function enum(string $name) : Builder\Enum_ {
|
||||
return new Builder\Enum_($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a trait use builder.
|
||||
*
|
||||
@ -161,6 +187,29 @@ class BuilderFactory
|
||||
return new Builder\Use_($name, Use_::TYPE_CONSTANT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a class constant builder.
|
||||
*
|
||||
* @param string|Identifier $name Name
|
||||
* @param Node\Expr|bool|null|int|float|string|array $value Value
|
||||
*
|
||||
* @return Builder\ClassConst The created use const builder
|
||||
*/
|
||||
public function classConst($name, $value) : Builder\ClassConst {
|
||||
return new Builder\ClassConst($name, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an enum case builder.
|
||||
*
|
||||
* @param string|Identifier $name Name
|
||||
*
|
||||
* @return Builder\EnumCase The created use const builder
|
||||
*/
|
||||
public function enumCase($name) : Builder\EnumCase {
|
||||
return new Builder\EnumCase($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates node a for a literal value.
|
||||
*
|
||||
@ -198,12 +247,14 @@ class BuilderFactory
|
||||
*/
|
||||
public function args(array $args) : array {
|
||||
$normalizedArgs = [];
|
||||
foreach ($args as $arg) {
|
||||
if ($arg instanceof Arg) {
|
||||
$normalizedArgs[] = $arg;
|
||||
} else {
|
||||
$normalizedArgs[] = new Arg(BuilderHelpers::normalizeValue($arg));
|
||||
foreach ($args as $key => $arg) {
|
||||
if (!($arg instanceof Arg)) {
|
||||
$arg = new Arg(BuilderHelpers::normalizeValue($arg));
|
||||
}
|
||||
if (\is_string($key)) {
|
||||
$arg->name = BuilderHelpers::normalizeIdentifier($key);
|
||||
}
|
||||
$normalizedArgs[] = $arg;
|
||||
}
|
||||
return $normalizedArgs;
|
||||
}
|
||||
@ -282,7 +333,7 @@ class BuilderFactory
|
||||
public function constFetch($name) : Expr\ConstFetch {
|
||||
return new Expr\ConstFetch(BuilderHelpers::normalizeName($name));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a property fetch node.
|
||||
*
|
||||
|
@ -2,13 +2,13 @@
|
||||
|
||||
namespace PhpParser;
|
||||
|
||||
use PhpParser\Node\ComplexType;
|
||||
use PhpParser\Node\Expr;
|
||||
use PhpParser\Node\Identifier;
|
||||
use PhpParser\Node\Name;
|
||||
use PhpParser\Node\NullableType;
|
||||
use PhpParser\Node\Scalar;
|
||||
use PhpParser\Node\Stmt;
|
||||
use PhpParser\Node\UnionType;
|
||||
|
||||
/**
|
||||
* This class defines helpers used in the implementation of builders. Don't use it directly.
|
||||
@ -27,7 +27,9 @@ final class BuilderHelpers
|
||||
public static function normalizeNode($node) : Node {
|
||||
if ($node instanceof Builder) {
|
||||
return $node->getNode();
|
||||
} elseif ($node instanceof Node) {
|
||||
}
|
||||
|
||||
if ($node instanceof Node) {
|
||||
return $node;
|
||||
}
|
||||
|
||||
@ -102,7 +104,27 @@ final class BuilderHelpers
|
||||
* @return Name The normalized name
|
||||
*/
|
||||
public static function normalizeName($name) : Name {
|
||||
return self::normalizeNameCommon($name, false);
|
||||
if ($name instanceof Name) {
|
||||
return $name;
|
||||
}
|
||||
|
||||
if (is_string($name)) {
|
||||
if (!$name) {
|
||||
throw new \LogicException('Name cannot be empty');
|
||||
}
|
||||
|
||||
if ($name[0] === '\\') {
|
||||
return new Name\FullyQualified(substr($name, 1));
|
||||
}
|
||||
|
||||
if (0 === strpos($name, 'namespace\\')) {
|
||||
return new Name\Relative(substr($name, strlen('namespace\\')));
|
||||
}
|
||||
|
||||
return new Name($name);
|
||||
}
|
||||
|
||||
throw new \LogicException('Name must be a string or an instance of Node\Name');
|
||||
}
|
||||
|
||||
/**
|
||||
@ -113,44 +135,17 @@ final class BuilderHelpers
|
||||
* @return Name|Expr The normalized name or expression
|
||||
*/
|
||||
public static function normalizeNameOrExpr($name) {
|
||||
return self::normalizeNameCommon($name, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes a name: Converts string names to Name nodes, optionally allowing expressions.
|
||||
*
|
||||
* @param Expr|Name|string $name The name to normalize
|
||||
* @param bool $allowExpr Whether to also allow expressions
|
||||
*
|
||||
* @return Name|Expr The normalized name, or expression (if allowed)
|
||||
*/
|
||||
private static function normalizeNameCommon($name, bool $allowExpr) {
|
||||
if ($name instanceof Name) {
|
||||
if ($name instanceof Expr) {
|
||||
return $name;
|
||||
} elseif (is_string($name)) {
|
||||
if (!$name) {
|
||||
throw new \LogicException('Name cannot be empty');
|
||||
}
|
||||
|
||||
if ($name[0] === '\\') {
|
||||
return new Name\FullyQualified(substr($name, 1));
|
||||
} elseif (0 === strpos($name, 'namespace\\')) {
|
||||
return new Name\Relative(substr($name, strlen('namespace\\')));
|
||||
} else {
|
||||
return new Name($name);
|
||||
}
|
||||
}
|
||||
|
||||
if ($allowExpr) {
|
||||
if ($name instanceof Expr) {
|
||||
return $name;
|
||||
}
|
||||
if (!is_string($name) && !($name instanceof Name)) {
|
||||
throw new \LogicException(
|
||||
'Name must be a string or an instance of Node\Name or Node\Expr'
|
||||
);
|
||||
} else {
|
||||
throw new \LogicException('Name must be a string or an instance of Node\Name');
|
||||
}
|
||||
|
||||
return self::normalizeName($name);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -159,18 +154,18 @@ final class BuilderHelpers
|
||||
* In particular, builtin types become Identifiers, custom types become Names and nullables
|
||||
* are wrapped in NullableType nodes.
|
||||
*
|
||||
* @param string|Name|Identifier|NullableType|UnionType $type The type to normalize
|
||||
* @param string|Name|Identifier|ComplexType $type The type to normalize
|
||||
*
|
||||
* @return Name|Identifier|NullableType|UnionType The normalized type
|
||||
* @return Name|Identifier|ComplexType The normalized type
|
||||
*/
|
||||
public static function normalizeType($type) {
|
||||
if (!is_string($type)) {
|
||||
if (
|
||||
!$type instanceof Name && !$type instanceof Identifier &&
|
||||
!$type instanceof NullableType && !$type instanceof UnionType
|
||||
!$type instanceof ComplexType
|
||||
) {
|
||||
throw new \LogicException(
|
||||
'Type must be a string, or an instance of Name, Identifier, NullableType or UnionType'
|
||||
'Type must be a string, or an instance of Name, Identifier or ComplexType'
|
||||
);
|
||||
}
|
||||
return $type;
|
||||
@ -183,7 +178,7 @@ final class BuilderHelpers
|
||||
}
|
||||
|
||||
$builtinTypes = [
|
||||
'array', 'callable', 'string', 'int', 'float', 'bool', 'iterable', 'void', 'object', 'mixed'
|
||||
'array', 'callable', 'string', 'int', 'float', 'bool', 'iterable', 'void', 'object', 'mixed', 'never',
|
||||
];
|
||||
|
||||
$lowerType = strtolower($type);
|
||||
@ -193,12 +188,11 @@ final class BuilderHelpers
|
||||
$type = self::normalizeName($type);
|
||||
}
|
||||
|
||||
if ($nullable && (string) $type === 'void') {
|
||||
throw new \LogicException('void type cannot be nullable');
|
||||
}
|
||||
|
||||
if ($nullable && (string) $type === 'mixed') {
|
||||
throw new \LogicException('mixed type cannot be nullable');
|
||||
$notNullableTypes = [
|
||||
'void', 'mixed', 'never',
|
||||
];
|
||||
if ($nullable && in_array((string) $type, $notNullableTypes)) {
|
||||
throw new \LogicException(sprintf('%s type cannot be nullable', $type));
|
||||
}
|
||||
|
||||
return $nullable ? new NullableType($type) : $type;
|
||||
@ -215,21 +209,33 @@ final class BuilderHelpers
|
||||
public static function normalizeValue($value) : Expr {
|
||||
if ($value instanceof Node\Expr) {
|
||||
return $value;
|
||||
} elseif (is_null($value)) {
|
||||
}
|
||||
|
||||
if (is_null($value)) {
|
||||
return new Expr\ConstFetch(
|
||||
new Name('null')
|
||||
);
|
||||
} elseif (is_bool($value)) {
|
||||
}
|
||||
|
||||
if (is_bool($value)) {
|
||||
return new Expr\ConstFetch(
|
||||
new Name($value ? 'true' : 'false')
|
||||
);
|
||||
} elseif (is_int($value)) {
|
||||
}
|
||||
|
||||
if (is_int($value)) {
|
||||
return new Scalar\LNumber($value);
|
||||
} elseif (is_float($value)) {
|
||||
}
|
||||
|
||||
if (is_float($value)) {
|
||||
return new Scalar\DNumber($value);
|
||||
} elseif (is_string($value)) {
|
||||
}
|
||||
|
||||
if (is_string($value)) {
|
||||
return new Scalar\String_($value);
|
||||
} elseif (is_array($value)) {
|
||||
}
|
||||
|
||||
if (is_array($value)) {
|
||||
$items = [];
|
||||
$lastKey = -1;
|
||||
foreach ($value as $itemKey => $itemValue) {
|
||||
@ -248,9 +254,9 @@ final class BuilderHelpers
|
||||
}
|
||||
|
||||
return new Expr\Array_($items);
|
||||
} else {
|
||||
throw new \LogicException('Invalid value');
|
||||
}
|
||||
|
||||
throw new \LogicException('Invalid value');
|
||||
}
|
||||
|
||||
/**
|
||||
@ -263,11 +269,33 @@ final class BuilderHelpers
|
||||
public static function normalizeDocComment($docComment) : Comment\Doc {
|
||||
if ($docComment instanceof Comment\Doc) {
|
||||
return $docComment;
|
||||
} elseif (is_string($docComment)) {
|
||||
return new Comment\Doc($docComment);
|
||||
} else {
|
||||
throw new \LogicException('Doc comment must be a string or an instance of PhpParser\Comment\Doc');
|
||||
}
|
||||
|
||||
if (is_string($docComment)) {
|
||||
return new Comment\Doc($docComment);
|
||||
}
|
||||
|
||||
throw new \LogicException('Doc comment must be a string or an instance of PhpParser\Comment\Doc');
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes a attribute: Converts attribute to the Attribute Group if needed.
|
||||
*
|
||||
* @param Node\Attribute|Node\AttributeGroup $attribute
|
||||
*
|
||||
* @return Node\AttributeGroup The Attribute Group
|
||||
*/
|
||||
public static function normalizeAttribute($attribute) : Node\AttributeGroup
|
||||
{
|
||||
if ($attribute instanceof Node\AttributeGroup) {
|
||||
return $attribute;
|
||||
}
|
||||
|
||||
if (!($attribute instanceof Node\Attribute)) {
|
||||
throw new \LogicException('Attribute must be an instance of PhpParser\Node\Attribute or PhpParser\Node\AttributeGroup');
|
||||
}
|
||||
|
||||
return new Node\AttributeGroup([$attribute]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
namespace PhpParser;
|
||||
|
||||
use function array_merge;
|
||||
use PhpParser\Node\Expr;
|
||||
use PhpParser\Node\Scalar;
|
||||
|
||||
@ -150,6 +151,8 @@ class ConstExprEvaluator
|
||||
foreach ($expr->items as $item) {
|
||||
if (null !== $item->key) {
|
||||
$array[$this->evaluate($item->key)] = $this->evaluate($item->value);
|
||||
} elseif ($item->unpack) {
|
||||
$array = array_merge($array, $this->evaluate($item->value));
|
||||
} else {
|
||||
$array[] = $this->evaluate($item->value);
|
||||
}
|
||||
|
@ -46,7 +46,8 @@ class TokenStream
|
||||
* @return bool
|
||||
*/
|
||||
public function haveBraces(int $startPos, int $endPos) : bool {
|
||||
return $this->haveTokenImmediatelyBefore($startPos, '{')
|
||||
return ($this->haveTokenImmediatelyBefore($startPos, '{')
|
||||
|| $this->haveTokenImmediatelyBefore($startPos, T_CURLY_OPEN))
|
||||
&& $this->haveTokenImmediatelyAfter($endPos, '}');
|
||||
}
|
||||
|
||||
@ -201,6 +202,7 @@ class TokenStream
|
||||
|
||||
public function haveBracesInRange(int $startPos, int $endPos) {
|
||||
return $this->haveTokenInRange($startPos, $endPos, '{')
|
||||
|| $this->haveTokenInRange($startPos, $endPos, T_CURLY_OPEN)
|
||||
|| $this->haveTokenInRange($startPos, $endPos, '}');
|
||||
}
|
||||
|
||||
|
@ -85,7 +85,6 @@ class Lexer
|
||||
|
||||
$scream = ini_set('xdebug.scream', '0');
|
||||
|
||||
error_clear_last();
|
||||
$this->tokens = @token_get_all($code);
|
||||
$this->postprocessTokens($errorHandler);
|
||||
|
||||
@ -135,10 +134,11 @@ class Lexer
|
||||
// detected by finding "gaps" in the token array. Unterminated comments are detected
|
||||
// by checking if a trailing comment has a "*/" at the end.
|
||||
//
|
||||
// Additionally, we canonicalize to the PHP 8 comment format here, which does not include
|
||||
// the trailing whitespace anymore.
|
||||
//
|
||||
// We also canonicalize to the PHP 8 T_NAME_* tokens.
|
||||
// Additionally, we perform a number of canonicalizations here:
|
||||
// * Use the PHP 8.0 comment format, which does not include trailing whitespace anymore.
|
||||
// * Use PHP 8.0 T_NAME_* tokens.
|
||||
// * Use PHP 8.1 T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG and
|
||||
// T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG tokens used to disambiguate intersection types.
|
||||
|
||||
$filePos = 0;
|
||||
$line = 1;
|
||||
@ -209,6 +209,22 @@ class Lexer
|
||||
}
|
||||
}
|
||||
|
||||
if ($token === '&') {
|
||||
$next = $i + 1;
|
||||
while (isset($this->tokens[$next]) && $this->tokens[$next][0] === \T_WHITESPACE) {
|
||||
$next++;
|
||||
}
|
||||
$followedByVarOrVarArg = isset($this->tokens[$next]) &&
|
||||
($this->tokens[$next][0] === \T_VARIABLE || $this->tokens[$next][0] === \T_ELLIPSIS);
|
||||
$this->tokens[$i] = $token = [
|
||||
$followedByVarOrVarArg
|
||||
? \T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG
|
||||
: \T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG,
|
||||
'&',
|
||||
$line,
|
||||
];
|
||||
}
|
||||
|
||||
$tokenValue = \is_string($token) ? $token : $token[1];
|
||||
$tokenLen = \strlen($tokenValue);
|
||||
|
||||
@ -323,7 +339,8 @@ class Lexer
|
||||
$value = $token[1];
|
||||
$id = $this->tokenMap[$token[0]];
|
||||
if (\T_CLOSE_TAG === $token[0]) {
|
||||
$this->prevCloseTagHasNewline = false !== strpos($token[1], "\n");
|
||||
$this->prevCloseTagHasNewline = false !== strpos($token[1], "\n")
|
||||
|| false !== strpos($token[1], "\r");
|
||||
} elseif (\T_INLINE_HTML === $token[0]) {
|
||||
$startAttributes['hasLeadingNewline'] = $this->prevCloseTagHasNewline;
|
||||
}
|
||||
@ -422,6 +439,11 @@ class Lexer
|
||||
'T_MATCH',
|
||||
'T_NULLSAFE_OBJECT_OPERATOR',
|
||||
'T_ATTRIBUTE',
|
||||
// PHP 8.1
|
||||
'T_ENUM',
|
||||
'T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG',
|
||||
'T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG',
|
||||
'T_READONLY',
|
||||
];
|
||||
|
||||
// PHP-Parser might be used together with another library that also emulates some or all
|
||||
@ -439,7 +461,7 @@ class Lexer
|
||||
$token, $clashingToken
|
||||
));
|
||||
}
|
||||
$usedTokenIds[$token] = $tokenId;
|
||||
$usedTokenIds[$tokenId] = $token;
|
||||
}
|
||||
}
|
||||
|
||||
@ -512,6 +534,10 @@ class Lexer
|
||||
$tokenMap[\T_MATCH] = Tokens::T_MATCH;
|
||||
$tokenMap[\T_NULLSAFE_OBJECT_OPERATOR] = Tokens::T_NULLSAFE_OBJECT_OPERATOR;
|
||||
$tokenMap[\T_ATTRIBUTE] = Tokens::T_ATTRIBUTE;
|
||||
$tokenMap[\T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG] = Tokens::T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG;
|
||||
$tokenMap[\T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG] = Tokens::T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG;
|
||||
$tokenMap[\T_ENUM] = Tokens::T_ENUM;
|
||||
$tokenMap[\T_READONLY] = Tokens::T_READONLY;
|
||||
|
||||
return $tokenMap;
|
||||
}
|
||||
@ -520,7 +546,7 @@ class Lexer
|
||||
// Based on semi_reserved production.
|
||||
return array_fill_keys([
|
||||
\T_STRING,
|
||||
\T_STATIC, \T_ABSTRACT, \T_FINAL, \T_PRIVATE, \T_PROTECTED, \T_PUBLIC,
|
||||
\T_STATIC, \T_ABSTRACT, \T_FINAL, \T_PRIVATE, \T_PROTECTED, \T_PUBLIC, \T_READONLY,
|
||||
\T_INCLUDE, \T_INCLUDE_ONCE, \T_EVAL, \T_REQUIRE, \T_REQUIRE_ONCE, \T_LOGICAL_OR, \T_LOGICAL_XOR, \T_LOGICAL_AND,
|
||||
\T_INSTANCEOF, \T_NEW, \T_CLONE, \T_EXIT, \T_IF, \T_ELSEIF, \T_ELSE, \T_ENDIF, \T_ECHO, \T_DO, \T_WHILE,
|
||||
\T_ENDWHILE, \T_FOR, \T_ENDFOR, \T_FOREACH, \T_ENDFOREACH, \T_DECLARE, \T_ENDDECLARE, \T_AS, \T_TRY, \T_CATCH,
|
||||
|
@ -6,21 +6,24 @@ use PhpParser\Error;
|
||||
use PhpParser\ErrorHandler;
|
||||
use PhpParser\Lexer;
|
||||
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\ReadonlyTokenEmulator;
|
||||
use PhpParser\Lexer\TokenEmulator\ReverseEmulator;
|
||||
use PhpParser\Lexer\TokenEmulator\TokenEmulator;
|
||||
use PhpParser\Parser\Tokens;
|
||||
|
||||
class Emulative extends Lexer
|
||||
{
|
||||
const PHP_7_3 = '7.3dev';
|
||||
const PHP_7_4 = '7.4dev';
|
||||
const PHP_8_0 = '8.0dev';
|
||||
const PHP_8_1 = '8.1dev';
|
||||
|
||||
/** @var mixed[] Patches used to reverse changes introduced in the code */
|
||||
private $patches = [];
|
||||
@ -34,11 +37,11 @@ class Emulative extends Lexer
|
||||
/**
|
||||
* @param mixed[] $options Lexer options. In addition to the usual options,
|
||||
* accepts a 'phpVersion' string that specifies the
|
||||
* version to emulated. Defaults to newest supported.
|
||||
* version to emulate. Defaults to newest supported.
|
||||
*/
|
||||
public function __construct(array $options = [])
|
||||
{
|
||||
$this->targetPhpVersion = $options['phpVersion'] ?? Emulative::PHP_8_0;
|
||||
$this->targetPhpVersion = $options['phpVersion'] ?? Emulative::PHP_8_1;
|
||||
unset($options['phpVersion']);
|
||||
|
||||
parent::__construct($options);
|
||||
@ -51,6 +54,9 @@ class Emulative extends Lexer
|
||||
new NumericLiteralSeparatorEmulator(),
|
||||
new NullsafeTokenEmulator(),
|
||||
new AttributeEmulator(),
|
||||
new EnumTokenEmulator(),
|
||||
new ReadonlyTokenEmulator(),
|
||||
new ExplicitOctalEmulator(),
|
||||
];
|
||||
|
||||
// Collect emulators that are relevant for the PHP version we're running
|
||||
|
31
lib/PhpParser/Lexer/TokenEmulator/EnumTokenEmulator.php
Normal file
31
lib/PhpParser/Lexer/TokenEmulator/EnumTokenEmulator.php
Normal file
@ -0,0 +1,31 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser\Lexer\TokenEmulator;
|
||||
|
||||
use PhpParser\Lexer\Emulative;
|
||||
|
||||
final class EnumTokenEmulator extends KeywordEmulator
|
||||
{
|
||||
public function getPhpVersion(): string
|
||||
{
|
||||
return Emulative::PHP_8_1;
|
||||
}
|
||||
|
||||
public function getKeywordString(): string
|
||||
{
|
||||
return 'enum';
|
||||
}
|
||||
|
||||
public function getKeywordToken(): int
|
||||
{
|
||||
return \T_ENUM;
|
||||
}
|
||||
|
||||
protected function isKeywordContext(array $tokens, int $pos): bool
|
||||
{
|
||||
return parent::isKeywordContext($tokens, $pos)
|
||||
&& isset($tokens[$pos + 2])
|
||||
&& $tokens[$pos + 1][0] === \T_WHITESPACE
|
||||
&& $tokens[$pos + 2][0] === \T_STRING;
|
||||
}
|
||||
}
|
44
lib/PhpParser/Lexer/TokenEmulator/ExplicitOctalEmulator.php
Normal file
44
lib/PhpParser/Lexer/TokenEmulator/ExplicitOctalEmulator.php
Normal file
@ -0,0 +1,44 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser\Lexer\TokenEmulator;
|
||||
|
||||
use PhpParser\Lexer\Emulative;
|
||||
|
||||
class ExplicitOctalEmulator extends TokenEmulator {
|
||||
public function getPhpVersion(): string {
|
||||
return Emulative::PHP_8_1;
|
||||
}
|
||||
|
||||
public function isEmulationNeeded(string $code): bool {
|
||||
return strpos($code, '0o') !== false || strpos($code, '0O') !== false;
|
||||
}
|
||||
|
||||
public function emulate(string $code, array $tokens): array {
|
||||
for ($i = 0, $c = count($tokens); $i < $c; ++$i) {
|
||||
if ($tokens[$i][0] == \T_LNUMBER && $tokens[$i][1] === '0' &&
|
||||
isset($tokens[$i + 1]) && $tokens[$i + 1][0] == \T_STRING &&
|
||||
preg_match('/[oO][0-7]+(?:_[0-7]+)*/', $tokens[$i + 1][1])
|
||||
) {
|
||||
$tokenKind = $this->resolveIntegerOrFloatToken($tokens[$i + 1][1]);
|
||||
array_splice($tokens, $i, 2, [
|
||||
[$tokenKind, '0' . $tokens[$i + 1][1], $tokens[$i][2]],
|
||||
]);
|
||||
$c--;
|
||||
}
|
||||
}
|
||||
return $tokens;
|
||||
}
|
||||
|
||||
private function resolveIntegerOrFloatToken(string $str): int
|
||||
{
|
||||
$str = substr($str, 1);
|
||||
$str = str_replace('_', '', $str);
|
||||
$num = octdec($str);
|
||||
return is_float($num) ? \T_DNUMBER : \T_LNUMBER;
|
||||
}
|
||||
|
||||
public function reverseEmulate(string $code, array $tokens): array {
|
||||
// Explicit octals were not legal code previously, don't bother.
|
||||
return $tokens;
|
||||
}
|
||||
}
|
@ -9,19 +9,21 @@ abstract class KeywordEmulator extends TokenEmulator
|
||||
|
||||
public function isEmulationNeeded(string $code): bool
|
||||
{
|
||||
return strpos($code, $this->getKeywordString()) !== false;
|
||||
return strpos(strtolower($code), $this->getKeywordString()) !== false;
|
||||
}
|
||||
|
||||
protected function isKeywordContext(array $tokens, int $pos): bool
|
||||
{
|
||||
$previousNonSpaceToken = $this->getPreviousNonSpaceToken($tokens, $pos);
|
||||
return $previousNonSpaceToken === null || $previousNonSpaceToken[0] !== \T_OBJECT_OPERATOR;
|
||||
}
|
||||
|
||||
public function emulate(string $code, array $tokens): array
|
||||
{
|
||||
$keywordString = $this->getKeywordString();
|
||||
foreach ($tokens as $i => $token) {
|
||||
if ($token[0] === T_STRING && strtolower($token[1]) === $keywordString) {
|
||||
$previousNonSpaceToken = $this->getPreviousNonSpaceToken($tokens, $i);
|
||||
if ($previousNonSpaceToken !== null && $previousNonSpaceToken[0] === \T_OBJECT_OPERATOR) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($token[0] === T_STRING && strtolower($token[1]) === $keywordString
|
||||
&& $this->isKeywordContext($tokens, $i)) {
|
||||
$tokens[$i][0] = $this->getKeywordToken();
|
||||
}
|
||||
}
|
||||
|
23
lib/PhpParser/Lexer/TokenEmulator/ReadonlyTokenEmulator.php
Normal file
23
lib/PhpParser/Lexer/TokenEmulator/ReadonlyTokenEmulator.php
Normal file
@ -0,0 +1,23 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser\Lexer\TokenEmulator;
|
||||
|
||||
use PhpParser\Lexer\Emulative;
|
||||
|
||||
final class ReadonlyTokenEmulator extends KeywordEmulator
|
||||
{
|
||||
public function getPhpVersion(): string
|
||||
{
|
||||
return Emulative::PHP_8_1;
|
||||
}
|
||||
|
||||
public function getKeywordString(): string
|
||||
{
|
||||
return 'readonly';
|
||||
}
|
||||
|
||||
public function getKeywordToken(): int
|
||||
{
|
||||
return \T_READONLY;
|
||||
}
|
||||
}
|
@ -2,6 +2,7 @@
|
||||
|
||||
namespace PhpParser\Node;
|
||||
|
||||
use PhpParser\Node\VariadicPlaceholder;
|
||||
use PhpParser\NodeAbstract;
|
||||
|
||||
class Arg extends NodeAbstract
|
||||
|
14
lib/PhpParser/Node/ComplexType.php
Normal file
14
lib/PhpParser/Node/ComplexType.php
Normal file
@ -0,0 +1,14 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser\Node;
|
||||
|
||||
use PhpParser\NodeAbstract;
|
||||
|
||||
/**
|
||||
* This is a base class for complex types, including nullable types and union types.
|
||||
*
|
||||
* It does not provide any shared behavior and exists only for type-checking purposes.
|
||||
*/
|
||||
abstract class ComplexType extends NodeAbstract
|
||||
{
|
||||
}
|
@ -4,9 +4,6 @@ namespace PhpParser\Node;
|
||||
|
||||
use PhpParser\NodeAbstract;
|
||||
|
||||
/**
|
||||
* @property Name $namespacedName Namespaced name (for global constants, if using NameResolver)
|
||||
*/
|
||||
class Const_ extends NodeAbstract
|
||||
{
|
||||
/** @var Identifier Name */
|
||||
@ -14,6 +11,9 @@ class Const_ extends NodeAbstract
|
||||
/** @var Expr Value */
|
||||
public $value;
|
||||
|
||||
/** @var Name Namespaced name (if using NameResolver) */
|
||||
public $namespacedName;
|
||||
|
||||
/**
|
||||
* Constructs a const node for use in class const and const statements.
|
||||
*
|
||||
|
@ -17,7 +17,7 @@ class ArrowFunction extends Expr implements FunctionLike
|
||||
/** @var Node\Param[] */
|
||||
public $params = [];
|
||||
|
||||
/** @var null|Node\Identifier|Node\Name|Node\NullableType|Node\UnionType */
|
||||
/** @var null|Node\Identifier|Node\Name|Node\ComplexType */
|
||||
public $returnType;
|
||||
|
||||
/** @var Expr */
|
||||
@ -42,7 +42,7 @@ class ArrowFunction extends Expr implements FunctionLike
|
||||
$this->params = $subNodes['params'] ?? [];
|
||||
$returnType = $subNodes['returnType'] ?? null;
|
||||
$this->returnType = \is_string($returnType) ? new Node\Identifier($returnType) : $returnType;
|
||||
$this->expr = $subNodes['expr'] ?? null;
|
||||
$this->expr = $subNodes['expr'];
|
||||
$this->attrGroups = $subNodes['attrGroups'] ?? [];
|
||||
}
|
||||
|
||||
|
39
lib/PhpParser/Node/Expr/CallLike.php
Normal file
39
lib/PhpParser/Node/Expr/CallLike.php
Normal file
@ -0,0 +1,39 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser\Node\Expr;
|
||||
|
||||
use PhpParser\Node\Arg;
|
||||
use PhpParser\Node\Expr;
|
||||
use PhpParser\Node\VariadicPlaceholder;
|
||||
|
||||
abstract class CallLike extends Expr {
|
||||
/**
|
||||
* Return raw arguments, which may be actual Args, or VariadicPlaceholders for first-class
|
||||
* callables.
|
||||
*
|
||||
* @return array<Arg|VariadicPlaceholder>
|
||||
*/
|
||||
abstract public function getRawArgs(): array;
|
||||
|
||||
/**
|
||||
* Returns whether this call expression is actually a first class callable.
|
||||
*/
|
||||
public function isFirstClassCallable(): bool {
|
||||
foreach ($this->getRawArgs() as $arg) {
|
||||
if ($arg instanceof VariadicPlaceholder) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that this is not a first-class callable and return only ordinary Args.
|
||||
*
|
||||
* @return Arg[]
|
||||
*/
|
||||
public function getArgs(): array {
|
||||
assert(!$this->isFirstClassCallable());
|
||||
return $this->getRawArgs();
|
||||
}
|
||||
}
|
@ -16,7 +16,7 @@ class Closure extends Expr implements FunctionLike
|
||||
public $params;
|
||||
/** @var ClosureUse[] use()s */
|
||||
public $uses;
|
||||
/** @var null|Node\Identifier|Node\Name|Node\NullableType|Node\UnionType Return type */
|
||||
/** @var null|Node\Identifier|Node\Name|Node\ComplexType Return type */
|
||||
public $returnType;
|
||||
/** @var Node\Stmt[] Statements */
|
||||
public $stmts;
|
||||
|
@ -5,19 +5,19 @@ namespace PhpParser\Node\Expr;
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Expr;
|
||||
|
||||
class FuncCall extends Expr
|
||||
class FuncCall extends CallLike
|
||||
{
|
||||
/** @var Node\Name|Expr Function name */
|
||||
public $name;
|
||||
/** @var Node\Arg[] Arguments */
|
||||
/** @var array<Node\Arg|Node\VariadicPlaceholder> Arguments */
|
||||
public $args;
|
||||
|
||||
/**
|
||||
* Constructs a function call node.
|
||||
*
|
||||
* @param Node\Name|Expr $name Function name
|
||||
* @param Node\Arg[] $args Arguments
|
||||
* @param array $attributes Additional attributes
|
||||
* @param Node\Name|Expr $name Function name
|
||||
* @param array<Node\Arg|Node\VariadicPlaceholder> $args Arguments
|
||||
* @param array $attributes Additional attributes
|
||||
*/
|
||||
public function __construct($name, array $args = [], array $attributes = []) {
|
||||
$this->attributes = $attributes;
|
||||
@ -32,4 +32,8 @@ class FuncCall extends Expr
|
||||
public function getType() : string {
|
||||
return 'Expr_FuncCall';
|
||||
}
|
||||
|
||||
public function getRawArgs(): array {
|
||||
return $this->args;
|
||||
}
|
||||
}
|
||||
|
@ -5,23 +5,24 @@ namespace PhpParser\Node\Expr;
|
||||
use PhpParser\Node\Arg;
|
||||
use PhpParser\Node\Expr;
|
||||
use PhpParser\Node\Identifier;
|
||||
use PhpParser\Node\VariadicPlaceholder;
|
||||
|
||||
class MethodCall extends Expr
|
||||
class MethodCall extends CallLike
|
||||
{
|
||||
/** @var Expr Variable holding object */
|
||||
public $var;
|
||||
/** @var Identifier|Expr Method name */
|
||||
public $name;
|
||||
/** @var Arg[] Arguments */
|
||||
/** @var array<Arg|VariadicPlaceholder> Arguments */
|
||||
public $args;
|
||||
|
||||
/**
|
||||
* Constructs a function call node.
|
||||
*
|
||||
* @param Expr $var Variable holding object
|
||||
* @param string|Identifier|Expr $name Method name
|
||||
* @param Arg[] $args Arguments
|
||||
* @param array $attributes Additional attributes
|
||||
* @param Expr $var Variable holding object
|
||||
* @param string|Identifier|Expr $name Method name
|
||||
* @param array<Arg|VariadicPlaceholder> $args Arguments
|
||||
* @param array $attributes Additional attributes
|
||||
*/
|
||||
public function __construct(Expr $var, $name, array $args = [], array $attributes = []) {
|
||||
$this->attributes = $attributes;
|
||||
@ -37,4 +38,8 @@ class MethodCall extends Expr
|
||||
public function getType() : string {
|
||||
return 'Expr_MethodCall';
|
||||
}
|
||||
|
||||
public function getRawArgs(): array {
|
||||
return $this->args;
|
||||
}
|
||||
}
|
||||
|
@ -3,20 +3,22 @@
|
||||
namespace PhpParser\Node\Expr;
|
||||
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Arg;
|
||||
use PhpParser\Node\Expr;
|
||||
use PhpParser\Node\VariadicPlaceholder;
|
||||
|
||||
class New_ extends Expr
|
||||
class New_ extends CallLike
|
||||
{
|
||||
/** @var Node\Name|Expr|Node\Stmt\Class_ Class name */
|
||||
public $class;
|
||||
/** @var Node\Arg[] Arguments */
|
||||
/** @var array<Arg|VariadicPlaceholder> Arguments */
|
||||
public $args;
|
||||
|
||||
/**
|
||||
* Constructs a function call node.
|
||||
*
|
||||
* @param Node\Name|Expr|Node\Stmt\Class_ $class Class name (or class node for anonymous classes)
|
||||
* @param Node\Arg[] $args Arguments
|
||||
* @param array<Arg|VariadicPlaceholder> $args Arguments
|
||||
* @param array $attributes Additional attributes
|
||||
*/
|
||||
public function __construct($class, array $args = [], array $attributes = []) {
|
||||
@ -32,4 +34,8 @@ class New_ extends Expr
|
||||
public function getType() : string {
|
||||
return 'Expr_New';
|
||||
}
|
||||
|
||||
public function getRawArgs(): array {
|
||||
return $this->args;
|
||||
}
|
||||
}
|
||||
|
@ -5,23 +5,24 @@ namespace PhpParser\Node\Expr;
|
||||
use PhpParser\Node\Arg;
|
||||
use PhpParser\Node\Expr;
|
||||
use PhpParser\Node\Identifier;
|
||||
use PhpParser\Node\VariadicPlaceholder;
|
||||
|
||||
class NullsafeMethodCall extends Expr
|
||||
class NullsafeMethodCall extends CallLike
|
||||
{
|
||||
/** @var Expr Variable holding object */
|
||||
public $var;
|
||||
/** @var Identifier|Expr Method name */
|
||||
public $name;
|
||||
/** @var Arg[] Arguments */
|
||||
/** @var array<Arg|VariadicPlaceholder> Arguments */
|
||||
public $args;
|
||||
|
||||
/**
|
||||
* Constructs a nullsafe method call node.
|
||||
*
|
||||
* @param Expr $var Variable holding object
|
||||
* @param string|Identifier|Expr $name Method name
|
||||
* @param Arg[] $args Arguments
|
||||
* @param array $attributes Additional attributes
|
||||
* @param Expr $var Variable holding object
|
||||
* @param string|Identifier|Expr $name Method name
|
||||
* @param array<Arg|VariadicPlaceholder> $args Arguments
|
||||
* @param array $attributes Additional attributes
|
||||
*/
|
||||
public function __construct(Expr $var, $name, array $args = [], array $attributes = []) {
|
||||
$this->attributes = $attributes;
|
||||
@ -37,4 +38,8 @@ class NullsafeMethodCall extends Expr
|
||||
public function getType() : string {
|
||||
return 'Expr_NullsafeMethodCall';
|
||||
}
|
||||
|
||||
public function getRawArgs(): array {
|
||||
return $this->args;
|
||||
}
|
||||
}
|
||||
|
@ -3,25 +3,27 @@
|
||||
namespace PhpParser\Node\Expr;
|
||||
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Arg;
|
||||
use PhpParser\Node\Expr;
|
||||
use PhpParser\Node\Identifier;
|
||||
use PhpParser\Node\VariadicPlaceholder;
|
||||
|
||||
class StaticCall extends Expr
|
||||
class StaticCall extends CallLike
|
||||
{
|
||||
/** @var Node\Name|Expr Class name */
|
||||
public $class;
|
||||
/** @var Identifier|Expr Method name */
|
||||
public $name;
|
||||
/** @var Node\Arg[] Arguments */
|
||||
/** @var array<Arg|VariadicPlaceholder> Arguments */
|
||||
public $args;
|
||||
|
||||
/**
|
||||
* Constructs a static method call node.
|
||||
*
|
||||
* @param Node\Name|Expr $class Class name
|
||||
* @param string|Identifier|Expr $name Method name
|
||||
* @param Node\Arg[] $args Arguments
|
||||
* @param array $attributes Additional attributes
|
||||
* @param Node\Name|Expr $class Class name
|
||||
* @param string|Identifier|Expr $name Method name
|
||||
* @param array<Arg|VariadicPlaceholder> $args Arguments
|
||||
* @param array $attributes Additional attributes
|
||||
*/
|
||||
public function __construct($class, $name, array $args = [], array $attributes = []) {
|
||||
$this->attributes = $attributes;
|
||||
@ -37,4 +39,8 @@ class StaticCall extends Expr
|
||||
public function getType() : string {
|
||||
return 'Expr_StaticCall';
|
||||
}
|
||||
|
||||
public function getRawArgs(): array {
|
||||
return $this->args;
|
||||
}
|
||||
}
|
||||
|
@ -13,7 +13,7 @@ class Variable extends Expr
|
||||
* Constructs a variable node.
|
||||
*
|
||||
* @param string|Expr $name Name
|
||||
* @param array $attributes Additional attributes
|
||||
* @param array $attributes Additional attributes
|
||||
*/
|
||||
public function __construct($name, array $attributes = []) {
|
||||
$this->attributes = $attributes;
|
||||
|
@ -23,7 +23,7 @@ interface FunctionLike extends Node
|
||||
/**
|
||||
* Get the declared return type or null
|
||||
*
|
||||
* @return null|Identifier|Name|NullableType|UnionType
|
||||
* @return null|Identifier|Name|ComplexType
|
||||
*/
|
||||
public function getReturnType();
|
||||
|
||||
|
30
lib/PhpParser/Node/IntersectionType.php
Normal file
30
lib/PhpParser/Node/IntersectionType.php
Normal file
@ -0,0 +1,30 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser\Node;
|
||||
|
||||
use PhpParser\NodeAbstract;
|
||||
|
||||
class IntersectionType extends ComplexType
|
||||
{
|
||||
/** @var (Identifier|Name)[] Types */
|
||||
public $types;
|
||||
|
||||
/**
|
||||
* Constructs an intersection type.
|
||||
*
|
||||
* @param (Identifier|Name)[] $types Types
|
||||
* @param array $attributes Additional attributes
|
||||
*/
|
||||
public function __construct(array $types, array $attributes = []) {
|
||||
$this->attributes = $attributes;
|
||||
$this->types = $types;
|
||||
}
|
||||
|
||||
public function getSubNodeNames() : array {
|
||||
return ['types'];
|
||||
}
|
||||
|
||||
public function getType() : string {
|
||||
return 'IntersectionType';
|
||||
}
|
||||
}
|
@ -2,9 +2,7 @@
|
||||
|
||||
namespace PhpParser\Node;
|
||||
|
||||
use PhpParser\NodeAbstract;
|
||||
|
||||
class NullableType extends NodeAbstract
|
||||
class NullableType extends ComplexType
|
||||
{
|
||||
/** @var Identifier|Name Type */
|
||||
public $type;
|
||||
|
@ -6,7 +6,7 @@ use PhpParser\NodeAbstract;
|
||||
|
||||
class Param extends NodeAbstract
|
||||
{
|
||||
/** @var null|Identifier|Name|NullableType|UnionType Type declaration */
|
||||
/** @var null|Identifier|Name|ComplexType Type declaration */
|
||||
public $type;
|
||||
/** @var bool Whether parameter is passed by reference */
|
||||
public $byRef;
|
||||
@ -24,14 +24,14 @@ class Param extends NodeAbstract
|
||||
/**
|
||||
* Constructs a parameter node.
|
||||
*
|
||||
* @param Expr\Variable|Expr\Error $var Parameter variable
|
||||
* @param null|Expr $default Default value
|
||||
* @param null|string|Identifier|Name|NullableType|UnionType $type Type declaration
|
||||
* @param bool $byRef Whether is passed by reference
|
||||
* @param bool $variadic Whether this is a variadic argument
|
||||
* @param array $attributes Additional attributes
|
||||
* @param int $flags Optional visibility flags
|
||||
* @param AttributeGroup[] $attrGroups PHP attribute groups
|
||||
* @param Expr\Variable|Expr\Error $var Parameter variable
|
||||
* @param null|Expr $default Default value
|
||||
* @param null|string|Identifier|Name|ComplexType $type Type declaration
|
||||
* @param bool $byRef Whether is passed by reference
|
||||
* @param bool $variadic Whether this is a variadic argument
|
||||
* @param array $attributes Additional attributes
|
||||
* @param int $flags Optional visibility flags
|
||||
* @param AttributeGroup[] $attrGroups PHP attribute groups
|
||||
*/
|
||||
public function __construct(
|
||||
$var, Expr $default = null, $type = null,
|
||||
|
@ -62,6 +62,11 @@ class LNumber extends Scalar
|
||||
throw new Error('Invalid numeric literal', $attributes);
|
||||
}
|
||||
|
||||
// Strip optional explicit octal prefix.
|
||||
if ('o' === $str[1] || 'O' === $str[1]) {
|
||||
$str = substr($str, 2);
|
||||
}
|
||||
|
||||
// use intval instead of octdec to get proper cutting behavior with malformed numbers
|
||||
$attributes['kind'] = LNumber::KIND_OCT;
|
||||
return new LNumber(intval($str, 8), $attributes);
|
||||
|
@ -65,6 +65,15 @@ class ClassConst extends Node\Stmt
|
||||
return (bool) ($this->flags & Class_::MODIFIER_PRIVATE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether constant is final.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isFinal() : bool {
|
||||
return (bool) ($this->flags & Class_::MODIFIER_FINAL);
|
||||
}
|
||||
|
||||
public function getType() : string {
|
||||
return 'Stmt_ClassConst';
|
||||
}
|
||||
|
@ -4,9 +4,6 @@ namespace PhpParser\Node\Stmt;
|
||||
|
||||
use PhpParser\Node;
|
||||
|
||||
/**
|
||||
* @property Node\Name $namespacedName Namespaced name (if using NameResolver)
|
||||
*/
|
||||
abstract class ClassLike extends Node\Stmt
|
||||
{
|
||||
/** @var Node\Identifier|null Name */
|
||||
@ -16,6 +13,9 @@ abstract class ClassLike extends Node\Stmt
|
||||
/** @var Node\AttributeGroup[] PHP attribute groups */
|
||||
public $attrGroups;
|
||||
|
||||
/** @var Node\Name Namespaced name (if using NameResolver) */
|
||||
public $namespacedName;
|
||||
|
||||
/**
|
||||
* @return TraitUse[]
|
||||
*/
|
||||
|
@ -15,7 +15,7 @@ class ClassMethod extends Node\Stmt implements FunctionLike
|
||||
public $name;
|
||||
/** @var Node\Param[] Parameters */
|
||||
public $params;
|
||||
/** @var null|Node\Identifier|Node\Name|Node\NullableType|Node\UnionType Return type */
|
||||
/** @var null|Node\Identifier|Node\Name|Node\ComplexType Return type */
|
||||
public $returnType;
|
||||
/** @var Node\Stmt[]|null Statements */
|
||||
public $stmts;
|
||||
|
@ -13,6 +13,7 @@ class Class_ extends ClassLike
|
||||
const MODIFIER_STATIC = 8;
|
||||
const MODIFIER_ABSTRACT = 16;
|
||||
const MODIFIER_FINAL = 32;
|
||||
const MODIFIER_READONLY = 64;
|
||||
|
||||
const VISIBILITY_MODIFIER_MASK = 7; // 1 | 2 | 4
|
||||
|
||||
@ -32,7 +33,7 @@ class Class_ extends ClassLike
|
||||
* 'extends' => null : Name of extended class
|
||||
* 'implements' => array(): Names of implemented interfaces
|
||||
* 'stmts' => array(): Statements
|
||||
* '$attrGroups' => array(): PHP attribute groups
|
||||
* 'attrGroups' => array(): PHP attribute groups
|
||||
* @param array $attributes Additional attributes
|
||||
*/
|
||||
public function __construct($name, array $subNodes = [], array $attributes = []) {
|
||||
@ -96,6 +97,10 @@ class Class_ extends ClassLike
|
||||
throw new Error('Multiple final modifiers are not allowed');
|
||||
}
|
||||
|
||||
if ($a & self::MODIFIER_READONLY && $b & self::MODIFIER_READONLY) {
|
||||
throw new Error('Multiple readonly modifiers are not allowed');
|
||||
}
|
||||
|
||||
if ($a & 48 && $b & 48) {
|
||||
throw new Error('Cannot use the final modifier on an abstract class member');
|
||||
}
|
||||
|
37
lib/PhpParser/Node/Stmt/EnumCase.php
Normal file
37
lib/PhpParser/Node/Stmt/EnumCase.php
Normal file
@ -0,0 +1,37 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser\Node\Stmt;
|
||||
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\AttributeGroup;
|
||||
|
||||
class EnumCase extends Node\Stmt
|
||||
{
|
||||
/** @var Node\Identifier Enum case name */
|
||||
public $name;
|
||||
/** @var Node\Expr|null Enum case expression */
|
||||
public $expr;
|
||||
/** @var Node\AttributeGroup[] PHP attribute groups */
|
||||
public $attrGroups;
|
||||
|
||||
/**
|
||||
* @param string|Node\Identifier $name Enum case name
|
||||
* @param Node\Expr|null $expr Enum case expression
|
||||
* @param AttributeGroup[] $attrGroups PHP attribute groups
|
||||
* @param array $attributes Additional attributes
|
||||
*/
|
||||
public function __construct($name, Node\Expr $expr = null, array $attrGroups = [], array $attributes = []) {
|
||||
parent::__construct($attributes);
|
||||
$this->name = \is_string($name) ? new Node\Identifier($name) : $name;
|
||||
$this->expr = $expr;
|
||||
$this->attrGroups = $attrGroups;
|
||||
}
|
||||
|
||||
public function getSubNodeNames() : array {
|
||||
return ['attrGroups', 'name', 'expr'];
|
||||
}
|
||||
|
||||
public function getType() : string {
|
||||
return 'Stmt_EnumCase';
|
||||
}
|
||||
}
|
40
lib/PhpParser/Node/Stmt/Enum_.php
Normal file
40
lib/PhpParser/Node/Stmt/Enum_.php
Normal file
@ -0,0 +1,40 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser\Node\Stmt;
|
||||
|
||||
use PhpParser\Node;
|
||||
|
||||
class Enum_ extends ClassLike
|
||||
{
|
||||
/** @var null|Node\Identifier Scalar Type */
|
||||
public $scalarType;
|
||||
/** @var Node\Name[] Names of implemented interfaces */
|
||||
public $implements;
|
||||
|
||||
/**
|
||||
* @param string|Node\Identifier|null $name Name
|
||||
* @param array $subNodes Array of the following optional subnodes:
|
||||
* 'scalarType' => null : Scalar type
|
||||
* 'implements' => array() : Names of implemented interfaces
|
||||
* 'stmts' => array() : Statements
|
||||
* 'attrGroups' => array() : PHP attribute groups
|
||||
* @param array $attributes Additional attributes
|
||||
*/
|
||||
public function __construct($name, array $subNodes = [], array $attributes = []) {
|
||||
$this->name = \is_string($name) ? new Node\Identifier($name) : $name;
|
||||
$this->scalarType = $subNodes['scalarType'] ?? null;
|
||||
$this->implements = $subNodes['implements'] ?? [];
|
||||
$this->stmts = $subNodes['stmts'] ?? [];
|
||||
$this->attrGroups = $subNodes['attrGroups'] ?? [];
|
||||
|
||||
parent::__construct($attributes);
|
||||
}
|
||||
|
||||
public function getSubNodeNames() : array {
|
||||
return ['attrGroups', 'name', 'scalarType', 'implements', 'stmts'];
|
||||
}
|
||||
|
||||
public function getType() : string {
|
||||
return 'Stmt_Enum';
|
||||
}
|
||||
}
|
@ -5,9 +5,6 @@ namespace PhpParser\Node\Stmt;
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\FunctionLike;
|
||||
|
||||
/**
|
||||
* @property Node\Name $namespacedName Namespaced name (if using NameResolver)
|
||||
*/
|
||||
class Function_ extends Node\Stmt implements FunctionLike
|
||||
{
|
||||
/** @var bool Whether function returns by reference */
|
||||
@ -16,13 +13,16 @@ class Function_ extends Node\Stmt implements FunctionLike
|
||||
public $name;
|
||||
/** @var Node\Param[] Parameters */
|
||||
public $params;
|
||||
/** @var null|Node\Identifier|Node\Name|Node\NullableType|Node\UnionType Return type */
|
||||
/** @var null|Node\Identifier|Node\Name|Node\ComplexType Return type */
|
||||
public $returnType;
|
||||
/** @var Node\Stmt[] Statements */
|
||||
public $stmts;
|
||||
/** @var Node\AttributeGroup[] PHP attribute groups */
|
||||
public $attrGroups;
|
||||
|
||||
/** @var Node\Name Namespaced name (if using NameResolver) */
|
||||
public $namespacedName;
|
||||
|
||||
/**
|
||||
* Constructs a function node.
|
||||
*
|
||||
|
@ -3,10 +3,9 @@
|
||||
namespace PhpParser\Node\Stmt;
|
||||
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\ComplexType;
|
||||
use PhpParser\Node\Identifier;
|
||||
use PhpParser\Node\Name;
|
||||
use PhpParser\Node\NullableType;
|
||||
use PhpParser\Node\UnionType;
|
||||
|
||||
class Property extends Node\Stmt
|
||||
{
|
||||
@ -14,7 +13,7 @@ class Property extends Node\Stmt
|
||||
public $flags;
|
||||
/** @var PropertyProperty[] Properties */
|
||||
public $props;
|
||||
/** @var null|Identifier|Name|NullableType|UnionType Type declaration */
|
||||
/** @var null|Identifier|Name|ComplexType Type declaration */
|
||||
public $type;
|
||||
/** @var Node\AttributeGroup[] PHP attribute groups */
|
||||
public $attrGroups;
|
||||
@ -22,11 +21,11 @@ class Property extends Node\Stmt
|
||||
/**
|
||||
* Constructs a class property list node.
|
||||
*
|
||||
* @param int $flags Modifiers
|
||||
* @param PropertyProperty[] $props Properties
|
||||
* @param array $attributes Additional attributes
|
||||
* @param null|string|Identifier|Name|NullableType|UnionType $type Type declaration
|
||||
* @param Node\AttributeGroup[] $attrGroups PHP attribute groups
|
||||
* @param int $flags Modifiers
|
||||
* @param PropertyProperty[] $props Properties
|
||||
* @param array $attributes Additional attributes
|
||||
* @param null|string|Identifier|Name|ComplexType $type Type declaration
|
||||
* @param Node\AttributeGroup[] $attrGroups PHP attribute groups
|
||||
*/
|
||||
public function __construct(int $flags, array $props, array $attributes = [], $type = null, array $attrGroups = []) {
|
||||
$this->attributes = $attributes;
|
||||
@ -77,6 +76,15 @@ class Property extends Node\Stmt
|
||||
return (bool) ($this->flags & Class_::MODIFIER_STATIC);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the property is readonly.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isReadonly() : bool {
|
||||
return (bool) ($this->flags & Class_::MODIFIER_READONLY);
|
||||
}
|
||||
|
||||
public function getType() : string {
|
||||
return 'Stmt_Property';
|
||||
}
|
||||
|
@ -2,9 +2,7 @@
|
||||
|
||||
namespace PhpParser\Node;
|
||||
|
||||
use PhpParser\NodeAbstract;
|
||||
|
||||
class UnionType extends NodeAbstract
|
||||
class UnionType extends ComplexType
|
||||
{
|
||||
/** @var (Identifier|Name)[] Types */
|
||||
public $types;
|
||||
|
27
lib/PhpParser/Node/VariadicPlaceholder.php
Normal file
27
lib/PhpParser/Node/VariadicPlaceholder.php
Normal file
@ -0,0 +1,27 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser\Node;
|
||||
|
||||
use PhpParser\NodeAbstract;
|
||||
|
||||
/**
|
||||
* Represents the "..." in "foo(...)" of the first-class callable syntax.
|
||||
*/
|
||||
class VariadicPlaceholder extends NodeAbstract {
|
||||
/**
|
||||
* Create a variadic argument placeholder (first-class callable syntax).
|
||||
*
|
||||
* @param array $attributes Additional attributes
|
||||
*/
|
||||
public function __construct(array $attributes = []) {
|
||||
$this->attributes = $attributes;
|
||||
}
|
||||
|
||||
public function getType(): string {
|
||||
return 'VariadicPlaceholder';
|
||||
}
|
||||
|
||||
public function getSubNodeNames(): array {
|
||||
return [];
|
||||
}
|
||||
}
|
@ -128,6 +128,9 @@ class NodeDumper
|
||||
if ($flags & Class_::MODIFIER_FINAL) {
|
||||
$strs[] = 'MODIFIER_FINAL';
|
||||
}
|
||||
if ($flags & Class_::MODIFIER_READONLY) {
|
||||
$strs[] = 'MODIFIER_READONLY';
|
||||
}
|
||||
|
||||
if ($strs) {
|
||||
return implode(' | ', $strs) . ' (' . $flags . ')';
|
||||
|
@ -86,6 +86,15 @@ class NameResolver extends NodeVisitorAbstract
|
||||
|
||||
$this->resolveAttrGroups($node);
|
||||
$this->addNamespacedName($node);
|
||||
} elseif ($node instanceof Stmt\Enum_) {
|
||||
foreach ($node->implements as &$interface) {
|
||||
$interface = $this->resolveClassName($interface);
|
||||
}
|
||||
|
||||
$this->resolveAttrGroups($node);
|
||||
if (null !== $node->name) {
|
||||
$this->addNamespacedName($node);
|
||||
}
|
||||
} elseif ($node instanceof Stmt\Trait_) {
|
||||
$this->resolveAttrGroups($node);
|
||||
$this->addNamespacedName($node);
|
||||
@ -110,6 +119,8 @@ class NameResolver extends NodeVisitorAbstract
|
||||
}
|
||||
} else if ($node instanceof Stmt\ClassConst) {
|
||||
$this->resolveAttrGroups($node);
|
||||
} else if ($node instanceof Stmt\EnumCase) {
|
||||
$this->resolveAttrGroups($node);
|
||||
} elseif ($node instanceof Expr\StaticCall
|
||||
|| $node instanceof Expr\StaticPropertyFetch
|
||||
|| $node instanceof Expr\ClassConstFetch
|
||||
@ -178,7 +189,7 @@ class NameResolver extends NodeVisitorAbstract
|
||||
$node->type = $this->resolveType($node->type);
|
||||
return $node;
|
||||
}
|
||||
if ($node instanceof Node\UnionType) {
|
||||
if ($node instanceof Node\UnionType || $node instanceof Node\IntersectionType) {
|
||||
foreach ($node->types as &$type) {
|
||||
$type = $this->resolveType($type);
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -35,110 +35,114 @@ final class Tokens
|
||||
const T_COALESCE = 283;
|
||||
const T_BOOLEAN_OR = 284;
|
||||
const T_BOOLEAN_AND = 285;
|
||||
const T_IS_EQUAL = 286;
|
||||
const T_IS_NOT_EQUAL = 287;
|
||||
const T_IS_IDENTICAL = 288;
|
||||
const T_IS_NOT_IDENTICAL = 289;
|
||||
const T_SPACESHIP = 290;
|
||||
const T_IS_SMALLER_OR_EQUAL = 291;
|
||||
const T_IS_GREATER_OR_EQUAL = 292;
|
||||
const T_SL = 293;
|
||||
const T_SR = 294;
|
||||
const T_INSTANCEOF = 295;
|
||||
const T_INC = 296;
|
||||
const T_DEC = 297;
|
||||
const T_INT_CAST = 298;
|
||||
const T_DOUBLE_CAST = 299;
|
||||
const T_STRING_CAST = 300;
|
||||
const T_ARRAY_CAST = 301;
|
||||
const T_OBJECT_CAST = 302;
|
||||
const T_BOOL_CAST = 303;
|
||||
const T_UNSET_CAST = 304;
|
||||
const T_POW = 305;
|
||||
const T_NEW = 306;
|
||||
const T_CLONE = 307;
|
||||
const T_EXIT = 308;
|
||||
const T_IF = 309;
|
||||
const T_ELSEIF = 310;
|
||||
const T_ELSE = 311;
|
||||
const T_ENDIF = 312;
|
||||
const T_LNUMBER = 313;
|
||||
const T_DNUMBER = 314;
|
||||
const T_STRING = 315;
|
||||
const T_STRING_VARNAME = 316;
|
||||
const T_VARIABLE = 317;
|
||||
const T_NUM_STRING = 318;
|
||||
const T_INLINE_HTML = 319;
|
||||
const T_ENCAPSED_AND_WHITESPACE = 320;
|
||||
const T_CONSTANT_ENCAPSED_STRING = 321;
|
||||
const T_ECHO = 322;
|
||||
const T_DO = 323;
|
||||
const T_WHILE = 324;
|
||||
const T_ENDWHILE = 325;
|
||||
const T_FOR = 326;
|
||||
const T_ENDFOR = 327;
|
||||
const T_FOREACH = 328;
|
||||
const T_ENDFOREACH = 329;
|
||||
const T_DECLARE = 330;
|
||||
const T_ENDDECLARE = 331;
|
||||
const T_AS = 332;
|
||||
const T_SWITCH = 333;
|
||||
const T_MATCH = 334;
|
||||
const T_ENDSWITCH = 335;
|
||||
const T_CASE = 336;
|
||||
const T_DEFAULT = 337;
|
||||
const T_BREAK = 338;
|
||||
const T_CONTINUE = 339;
|
||||
const T_GOTO = 340;
|
||||
const T_FUNCTION = 341;
|
||||
const T_FN = 342;
|
||||
const T_CONST = 343;
|
||||
const T_RETURN = 344;
|
||||
const T_TRY = 345;
|
||||
const T_CATCH = 346;
|
||||
const T_FINALLY = 347;
|
||||
const T_USE = 348;
|
||||
const T_INSTEADOF = 349;
|
||||
const T_GLOBAL = 350;
|
||||
const T_STATIC = 351;
|
||||
const T_ABSTRACT = 352;
|
||||
const T_FINAL = 353;
|
||||
const T_PRIVATE = 354;
|
||||
const T_PROTECTED = 355;
|
||||
const T_PUBLIC = 356;
|
||||
const T_VAR = 357;
|
||||
const T_UNSET = 358;
|
||||
const T_ISSET = 359;
|
||||
const T_EMPTY = 360;
|
||||
const T_HALT_COMPILER = 361;
|
||||
const T_CLASS = 362;
|
||||
const T_TRAIT = 363;
|
||||
const T_INTERFACE = 364;
|
||||
const T_EXTENDS = 365;
|
||||
const T_IMPLEMENTS = 366;
|
||||
const T_OBJECT_OPERATOR = 367;
|
||||
const T_NULLSAFE_OBJECT_OPERATOR = 368;
|
||||
const T_LIST = 369;
|
||||
const T_ARRAY = 370;
|
||||
const T_CALLABLE = 371;
|
||||
const T_CLASS_C = 372;
|
||||
const T_TRAIT_C = 373;
|
||||
const T_METHOD_C = 374;
|
||||
const T_FUNC_C = 375;
|
||||
const T_LINE = 376;
|
||||
const T_FILE = 377;
|
||||
const T_START_HEREDOC = 378;
|
||||
const T_END_HEREDOC = 379;
|
||||
const T_DOLLAR_OPEN_CURLY_BRACES = 380;
|
||||
const T_CURLY_OPEN = 381;
|
||||
const T_PAAMAYIM_NEKUDOTAYIM = 382;
|
||||
const T_NAMESPACE = 383;
|
||||
const T_NS_C = 384;
|
||||
const T_DIR = 385;
|
||||
const T_NS_SEPARATOR = 386;
|
||||
const T_ELLIPSIS = 387;
|
||||
const T_NAME_FULLY_QUALIFIED = 388;
|
||||
const T_NAME_QUALIFIED = 389;
|
||||
const T_NAME_RELATIVE = 390;
|
||||
const T_ATTRIBUTE = 391;
|
||||
const T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG = 286;
|
||||
const T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG = 287;
|
||||
const T_IS_EQUAL = 288;
|
||||
const T_IS_NOT_EQUAL = 289;
|
||||
const T_IS_IDENTICAL = 290;
|
||||
const T_IS_NOT_IDENTICAL = 291;
|
||||
const T_SPACESHIP = 292;
|
||||
const T_IS_SMALLER_OR_EQUAL = 293;
|
||||
const T_IS_GREATER_OR_EQUAL = 294;
|
||||
const T_SL = 295;
|
||||
const T_SR = 296;
|
||||
const T_INSTANCEOF = 297;
|
||||
const T_INC = 298;
|
||||
const T_DEC = 299;
|
||||
const T_INT_CAST = 300;
|
||||
const T_DOUBLE_CAST = 301;
|
||||
const T_STRING_CAST = 302;
|
||||
const T_ARRAY_CAST = 303;
|
||||
const T_OBJECT_CAST = 304;
|
||||
const T_BOOL_CAST = 305;
|
||||
const T_UNSET_CAST = 306;
|
||||
const T_POW = 307;
|
||||
const T_NEW = 308;
|
||||
const T_CLONE = 309;
|
||||
const T_EXIT = 310;
|
||||
const T_IF = 311;
|
||||
const T_ELSEIF = 312;
|
||||
const T_ELSE = 313;
|
||||
const T_ENDIF = 314;
|
||||
const T_LNUMBER = 315;
|
||||
const T_DNUMBER = 316;
|
||||
const T_STRING = 317;
|
||||
const T_STRING_VARNAME = 318;
|
||||
const T_VARIABLE = 319;
|
||||
const T_NUM_STRING = 320;
|
||||
const T_INLINE_HTML = 321;
|
||||
const T_ENCAPSED_AND_WHITESPACE = 322;
|
||||
const T_CONSTANT_ENCAPSED_STRING = 323;
|
||||
const T_ECHO = 324;
|
||||
const T_DO = 325;
|
||||
const T_WHILE = 326;
|
||||
const T_ENDWHILE = 327;
|
||||
const T_FOR = 328;
|
||||
const T_ENDFOR = 329;
|
||||
const T_FOREACH = 330;
|
||||
const T_ENDFOREACH = 331;
|
||||
const T_DECLARE = 332;
|
||||
const T_ENDDECLARE = 333;
|
||||
const T_AS = 334;
|
||||
const T_SWITCH = 335;
|
||||
const T_MATCH = 336;
|
||||
const T_ENDSWITCH = 337;
|
||||
const T_CASE = 338;
|
||||
const T_DEFAULT = 339;
|
||||
const T_BREAK = 340;
|
||||
const T_CONTINUE = 341;
|
||||
const T_GOTO = 342;
|
||||
const T_FUNCTION = 343;
|
||||
const T_FN = 344;
|
||||
const T_CONST = 345;
|
||||
const T_RETURN = 346;
|
||||
const T_TRY = 347;
|
||||
const T_CATCH = 348;
|
||||
const T_FINALLY = 349;
|
||||
const T_USE = 350;
|
||||
const T_INSTEADOF = 351;
|
||||
const T_GLOBAL = 352;
|
||||
const T_STATIC = 353;
|
||||
const T_ABSTRACT = 354;
|
||||
const T_FINAL = 355;
|
||||
const T_PRIVATE = 356;
|
||||
const T_PROTECTED = 357;
|
||||
const T_PUBLIC = 358;
|
||||
const T_READONLY = 359;
|
||||
const T_VAR = 360;
|
||||
const T_UNSET = 361;
|
||||
const T_ISSET = 362;
|
||||
const T_EMPTY = 363;
|
||||
const T_HALT_COMPILER = 364;
|
||||
const T_CLASS = 365;
|
||||
const T_TRAIT = 366;
|
||||
const T_INTERFACE = 367;
|
||||
const T_ENUM = 368;
|
||||
const T_EXTENDS = 369;
|
||||
const T_IMPLEMENTS = 370;
|
||||
const T_OBJECT_OPERATOR = 371;
|
||||
const T_NULLSAFE_OBJECT_OPERATOR = 372;
|
||||
const T_LIST = 373;
|
||||
const T_ARRAY = 374;
|
||||
const T_CALLABLE = 375;
|
||||
const T_CLASS_C = 376;
|
||||
const T_TRAIT_C = 377;
|
||||
const T_METHOD_C = 378;
|
||||
const T_FUNC_C = 379;
|
||||
const T_LINE = 380;
|
||||
const T_FILE = 381;
|
||||
const T_START_HEREDOC = 382;
|
||||
const T_END_HEREDOC = 383;
|
||||
const T_DOLLAR_OPEN_CURLY_BRACES = 384;
|
||||
const T_CURLY_OPEN = 385;
|
||||
const T_PAAMAYIM_NEKUDOTAYIM = 386;
|
||||
const T_NAMESPACE = 387;
|
||||
const T_NS_C = 388;
|
||||
const T_DIR = 389;
|
||||
const T_NS_SEPARATOR = 390;
|
||||
const T_ELLIPSIS = 391;
|
||||
const T_NAME_FULLY_QUALIFIED = 392;
|
||||
const T_NAME_QUALIFIED = 393;
|
||||
const T_NAME_RELATIVE = 394;
|
||||
const T_ATTRIBUTE = 395;
|
||||
}
|
||||
|
@ -16,6 +16,7 @@ use PhpParser\Node\Scalar\String_;
|
||||
use PhpParser\Node\Stmt\Class_;
|
||||
use PhpParser\Node\Stmt\ClassConst;
|
||||
use PhpParser\Node\Stmt\ClassMethod;
|
||||
use PhpParser\Node\Stmt\Enum_;
|
||||
use PhpParser\Node\Stmt\Interface_;
|
||||
use PhpParser\Node\Stmt\Namespace_;
|
||||
use PhpParser\Node\Stmt\Property;
|
||||
@ -60,7 +61,7 @@ abstract class ParserAbstract implements Parser
|
||||
|
||||
/** @var int[] Map of states to a displacement into the $action table. The corresponding action for this
|
||||
* state/symbol pair is $action[$actionBase[$state] + $symbol]. If $actionBase[$state] is 0, the
|
||||
action is defaulted, i.e. $actionDefault[$state] should be used instead. */
|
||||
* action is defaulted, i.e. $actionDefault[$state] should be used instead. */
|
||||
protected $actionBase;
|
||||
/** @var int[] Table of actions. Indexed according to $actionBase comment. */
|
||||
protected $action;
|
||||
@ -219,10 +220,7 @@ abstract class ParserAbstract implements Parser
|
||||
));
|
||||
}
|
||||
|
||||
// This is necessary to assign some meaningful attributes to /* empty */ productions. They'll get
|
||||
// the attributes of the next token, even though they don't contain it themselves.
|
||||
$this->startAttributeStack[$stackPos+1] = $startAttributes;
|
||||
$this->endAttributeStack[$stackPos+1] = $endAttributes;
|
||||
// Allow productions to access the start attributes of the lookahead token.
|
||||
$this->lookaheadStartAttributes = $startAttributes;
|
||||
|
||||
//$this->traceRead($symbol);
|
||||
@ -294,7 +292,8 @@ abstract class ParserAbstract implements Parser
|
||||
|
||||
/* Goto - shift nonterminal */
|
||||
$lastEndAttributes = $this->endAttributeStack[$stackPos];
|
||||
$stackPos -= $this->ruleToLength[$rule];
|
||||
$ruleLength = $this->ruleToLength[$rule];
|
||||
$stackPos -= $ruleLength;
|
||||
$nonTerminal = $this->ruleToNonTerminal[$rule];
|
||||
$idx = $this->gotoBase[$nonTerminal] + $stateStack[$stackPos];
|
||||
if ($idx >= 0 && $idx < $this->gotoTableSize && $this->gotoCheck[$idx] === $nonTerminal) {
|
||||
@ -307,6 +306,10 @@ abstract class ParserAbstract implements Parser
|
||||
$stateStack[$stackPos] = $state;
|
||||
$this->semStack[$stackPos] = $this->semValue;
|
||||
$this->endAttributeStack[$stackPos] = $lastEndAttributes;
|
||||
if ($ruleLength === 0) {
|
||||
// Empty productions use the start attributes of the lookahead token.
|
||||
$this->startAttributeStack[$stackPos] = $this->lookaheadStartAttributes;
|
||||
}
|
||||
} else {
|
||||
/* error */
|
||||
switch ($this->errorState) {
|
||||
@ -340,6 +343,7 @@ abstract class ParserAbstract implements Parser
|
||||
|
||||
// We treat the error symbol as being empty, so we reset the end attributes
|
||||
// to the end attributes of the last non-error symbol
|
||||
$this->startAttributeStack[$stackPos] = $this->lookaheadStartAttributes;
|
||||
$this->endAttributeStack[$stackPos] = $this->endAttributeStack[$stackPos - 1];
|
||||
$this->endAttributes = $this->endAttributeStack[$stackPos - 1];
|
||||
break;
|
||||
@ -659,6 +663,7 @@ abstract class ParserAbstract implements Parser
|
||||
'null' => true,
|
||||
'false' => true,
|
||||
'mixed' => true,
|
||||
'never' => true,
|
||||
];
|
||||
|
||||
if (!$name->isUnqualified()) {
|
||||
@ -909,22 +914,17 @@ abstract class ParserAbstract implements Parser
|
||||
}
|
||||
}
|
||||
|
||||
protected function checkClass(Class_ $node, $namePos) {
|
||||
if (null !== $node->name && $node->name->isSpecialClassName()) {
|
||||
private function checkClassName($name, $namePos) {
|
||||
if (null !== $name && $name->isSpecialClassName()) {
|
||||
$this->emitError(new Error(
|
||||
sprintf('Cannot use \'%s\' as class name as it is reserved', $node->name),
|
||||
sprintf('Cannot use \'%s\' as class name as it is reserved', $name),
|
||||
$this->getAttributesAt($namePos)
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
if ($node->extends && $node->extends->isSpecialClassName()) {
|
||||
$this->emitError(new Error(
|
||||
sprintf('Cannot use \'%s\' as class name as it is reserved', $node->extends),
|
||||
$node->extends->getAttributes()
|
||||
));
|
||||
}
|
||||
|
||||
foreach ($node->implements as $interface) {
|
||||
private function checkImplementedInterfaces(array $interfaces) {
|
||||
foreach ($interfaces as $interface) {
|
||||
if ($interface->isSpecialClassName()) {
|
||||
$this->emitError(new Error(
|
||||
sprintf('Cannot use \'%s\' as interface name as it is reserved', $interface),
|
||||
@ -934,22 +934,27 @@ abstract class ParserAbstract implements Parser
|
||||
}
|
||||
}
|
||||
|
||||
protected function checkInterface(Interface_ $node, $namePos) {
|
||||
if (null !== $node->name && $node->name->isSpecialClassName()) {
|
||||
protected function checkClass(Class_ $node, $namePos) {
|
||||
$this->checkClassName($node->name, $namePos);
|
||||
|
||||
if ($node->extends && $node->extends->isSpecialClassName()) {
|
||||
$this->emitError(new Error(
|
||||
sprintf('Cannot use \'%s\' as class name as it is reserved', $node->name),
|
||||
$this->getAttributesAt($namePos)
|
||||
sprintf('Cannot use \'%s\' as class name as it is reserved', $node->extends),
|
||||
$node->extends->getAttributes()
|
||||
));
|
||||
}
|
||||
|
||||
foreach ($node->extends as $interface) {
|
||||
if ($interface->isSpecialClassName()) {
|
||||
$this->emitError(new Error(
|
||||
sprintf('Cannot use \'%s\' as interface name as it is reserved', $interface),
|
||||
$interface->getAttributes()
|
||||
));
|
||||
}
|
||||
}
|
||||
$this->checkImplementedInterfaces($node->implements);
|
||||
}
|
||||
|
||||
protected function checkInterface(Interface_ $node, $namePos) {
|
||||
$this->checkClassName($node->name, $namePos);
|
||||
$this->checkImplementedInterfaces($node->extends);
|
||||
}
|
||||
|
||||
protected function checkEnum(Enum_ $node, $namePos) {
|
||||
$this->checkClassName($node->name, $namePos);
|
||||
$this->checkImplementedInterfaces($node->implements);
|
||||
}
|
||||
|
||||
protected function checkClassMethod(ClassMethod $node, $modifierPos) {
|
||||
@ -972,6 +977,12 @@ abstract class ParserAbstract implements Parser
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($node->flags & Class_::MODIFIER_READONLY) {
|
||||
$this->emitError(new Error(
|
||||
sprintf('Method %s() cannot be readonly', $node->name),
|
||||
$this->getAttributesAt($modifierPos)));
|
||||
}
|
||||
}
|
||||
|
||||
protected function checkClassConst(ClassConst $node, $modifierPos) {
|
||||
@ -985,9 +996,9 @@ abstract class ParserAbstract implements Parser
|
||||
"Cannot use 'abstract' as constant modifier",
|
||||
$this->getAttributesAt($modifierPos)));
|
||||
}
|
||||
if ($node->flags & Class_::MODIFIER_FINAL) {
|
||||
if ($node->flags & Class_::MODIFIER_READONLY) {
|
||||
$this->emitError(new Error(
|
||||
"Cannot use 'final' as constant modifier",
|
||||
"Cannot use 'readonly' as constant modifier",
|
||||
$this->getAttributesAt($modifierPos)));
|
||||
}
|
||||
}
|
||||
|
@ -33,6 +33,10 @@ class Standard extends PrettyPrinterAbstract
|
||||
. $this->p($node->value);
|
||||
}
|
||||
|
||||
protected function pVariadicPlaceholder(Node\VariadicPlaceholder $node) {
|
||||
return '...';
|
||||
}
|
||||
|
||||
protected function pConst(Node\Const_ $node) {
|
||||
return $node->name . ' = ' . $this->p($node->value);
|
||||
}
|
||||
@ -45,6 +49,10 @@ class Standard extends PrettyPrinterAbstract
|
||||
return $this->pImplode($node->types, '|');
|
||||
}
|
||||
|
||||
protected function pIntersectionType(Node\IntersectionType $node) {
|
||||
return $this->pImplode($node->types, '&');
|
||||
}
|
||||
|
||||
protected function pIdentifier(Node\Identifier $node) {
|
||||
return $node->name;
|
||||
}
|
||||
@ -727,6 +735,14 @@ class Standard extends PrettyPrinterAbstract
|
||||
. $this->nl . '{' . $this->pStmts($node->stmts) . $this->nl . '}';
|
||||
}
|
||||
|
||||
protected function pStmt_Enum(Stmt\Enum_ $node) {
|
||||
return $this->pAttrGroups($node->attrGroups)
|
||||
. 'enum ' . $node->name
|
||||
. ($node->scalarType ? " : $node->scalarType" : '')
|
||||
. (!empty($node->implements) ? ' implements ' . $this->pCommaSeparated($node->implements) : '')
|
||||
. $this->nl . '{' . $this->pStmts($node->stmts) . $this->nl . '}';
|
||||
}
|
||||
|
||||
protected function pStmt_Class(Stmt\Class_ $node) {
|
||||
return $this->pClassCommon($node, ' ' . $node->name);
|
||||
}
|
||||
@ -737,6 +753,13 @@ class Standard extends PrettyPrinterAbstract
|
||||
. $this->nl . '{' . $this->pStmts($node->stmts) . $this->nl . '}';
|
||||
}
|
||||
|
||||
protected function pStmt_EnumCase(Stmt\EnumCase $node) {
|
||||
return $this->pAttrGroups($node->attrGroups)
|
||||
. 'case ' . $node->name
|
||||
. ($node->expr ? ' = ' . $this->p($node->expr) : '')
|
||||
. ';';
|
||||
}
|
||||
|
||||
protected function pStmt_TraitUse(Stmt\TraitUse $node) {
|
||||
return 'use ' . $this->pCommaSeparated($node->traits)
|
||||
. (empty($node->adaptations)
|
||||
@ -984,14 +1007,27 @@ class Standard extends PrettyPrinterAbstract
|
||||
$escaped = addcslashes($string, "\n\r\t\f\v$" . $quote . "\\");
|
||||
}
|
||||
|
||||
// Escape other control characters
|
||||
return preg_replace_callback('/([\0-\10\16-\37])(?=([0-7]?))/', function ($matches) {
|
||||
$oct = decoct(ord($matches[1]));
|
||||
if ($matches[2] !== '') {
|
||||
// If there is a trailing digit, use the full three character form
|
||||
return '\\' . str_pad($oct, 3, '0', \STR_PAD_LEFT);
|
||||
}
|
||||
return '\\' . $oct;
|
||||
// Escape control characters and non-UTF-8 characters.
|
||||
// Regex based on https://stackoverflow.com/a/11709412/385378.
|
||||
$regex = '/(
|
||||
[\x00-\x08\x0E-\x1F] # Control characters
|
||||
| [\xC0-\xC1] # Invalid UTF-8 Bytes
|
||||
| [\xF5-\xFF] # Invalid UTF-8 Bytes
|
||||
| \xE0(?=[\x80-\x9F]) # Overlong encoding of prior code point
|
||||
| \xF0(?=[\x80-\x8F]) # Overlong encoding of prior code point
|
||||
| [\xC2-\xDF](?![\x80-\xBF]) # Invalid UTF-8 Sequence Start
|
||||
| [\xE0-\xEF](?![\x80-\xBF]{2}) # Invalid UTF-8 Sequence Start
|
||||
| [\xF0-\xF4](?![\x80-\xBF]{3}) # Invalid UTF-8 Sequence Start
|
||||
| (?<=[\x00-\x7F\xF5-\xFF])[\x80-\xBF] # Invalid UTF-8 Sequence Middle
|
||||
| (?<![\xC2-\xDF]|[\xE0-\xEF]|[\xE0-\xEF][\x80-\xBF]|[\xF0-\xF4]|[\xF0-\xF4][\x80-\xBF]|[\xF0-\xF4][\x80-\xBF]{2})[\x80-\xBF] # Overlong Sequence
|
||||
| (?<=[\xE0-\xEF])[\x80-\xBF](?![\x80-\xBF]) # Short 3 byte sequence
|
||||
| (?<=[\xF0-\xF4])[\x80-\xBF](?![\x80-\xBF]{2}) # Short 4 byte sequence
|
||||
| (?<=[\xF0-\xF4][\x80-\xBF])[\x80-\xBF](?![\x80-\xBF]) # Short 4 byte sequence (2)
|
||||
)/x';
|
||||
return preg_replace_callback($regex, function ($matches) {
|
||||
assert(strlen($matches[0]) === 1);
|
||||
$hex = dechex(ord($matches[0]));;
|
||||
return '\\x' . str_pad($hex, 2, '0', \STR_PAD_LEFT);
|
||||
}, $escaped);
|
||||
}
|
||||
|
||||
@ -1040,7 +1076,7 @@ class Standard extends PrettyPrinterAbstract
|
||||
* @param Node[] $nodes
|
||||
* @return bool
|
||||
*/
|
||||
private function hasNodeWithComments(array $nodes) {
|
||||
protected function hasNodeWithComments(array $nodes) {
|
||||
foreach ($nodes as $node) {
|
||||
if ($node && $node->getComments()) {
|
||||
return true;
|
||||
@ -1049,7 +1085,7 @@ class Standard extends PrettyPrinterAbstract
|
||||
return false;
|
||||
}
|
||||
|
||||
private function pMaybeMultiline(array $nodes, bool $trailingComma = false) {
|
||||
protected function pMaybeMultiline(array $nodes, bool $trailingComma = false) {
|
||||
if (!$this->hasNodeWithComments($nodes)) {
|
||||
return $this->pCommaSeparated($nodes);
|
||||
} else {
|
||||
@ -1057,7 +1093,7 @@ class Standard extends PrettyPrinterAbstract
|
||||
}
|
||||
}
|
||||
|
||||
private function pAttrGroups(array $nodes, bool $inline = false): string {
|
||||
protected function pAttrGroups(array $nodes, bool $inline = false): string {
|
||||
$result = '';
|
||||
$sep = $inline ? ' ' : $this->nl;
|
||||
foreach ($nodes as $node) {
|
||||
|
@ -66,7 +66,7 @@ abstract class PrettyPrinterAbstract
|
||||
BinaryOp\BooleanAnd::class => [120, -1],
|
||||
BinaryOp\BooleanOr::class => [130, -1],
|
||||
BinaryOp\Coalesce::class => [140, 1],
|
||||
Expr\Ternary::class => [150, -1],
|
||||
Expr\Ternary::class => [150, 0],
|
||||
// parser uses %left for assignments, but they really behave as %right
|
||||
Expr\Assign::class => [160, 1],
|
||||
Expr\AssignRef::class => [160, 1],
|
||||
@ -756,7 +756,7 @@ abstract class PrettyPrinterAbstract
|
||||
|
||||
$itemStartPos = $origArrItem->getStartTokenPos();
|
||||
$itemEndPos = $origArrItem->getEndTokenPos();
|
||||
\assert($itemStartPos >= 0 && $itemEndPos >= 0);
|
||||
\assert($itemStartPos >= 0 && $itemEndPos >= 0 && $itemStartPos >= $pos);
|
||||
|
||||
$origIndentLevel = $this->indentLevel;
|
||||
$lastElemIndentLevel = $this->origTokens->getIndentationBefore($itemStartPos) + $indentAdjustment;
|
||||
@ -767,16 +767,25 @@ abstract class PrettyPrinterAbstract
|
||||
$commentStartPos = $origComments ? $origComments[0]->getStartTokenPos() : $itemStartPos;
|
||||
\assert($commentStartPos >= 0);
|
||||
|
||||
$commentsChanged = $comments !== $origComments;
|
||||
if ($commentsChanged) {
|
||||
// Remove old comments
|
||||
$itemStartPos = $commentStartPos;
|
||||
if ($commentStartPos < $pos) {
|
||||
// Comments may be assigned to multiple nodes if they start at the same position.
|
||||
// Make sure we don't try to print them multiple times.
|
||||
$commentStartPos = $itemStartPos;
|
||||
}
|
||||
|
||||
if ($skipRemovedNode) {
|
||||
if ($isStmtList && $this->origTokens->haveBracesInRange($pos, $itemStartPos)) {
|
||||
// We'd remove the brace of a code block.
|
||||
// TODO: Preserve formatting.
|
||||
$this->setIndentLevel($origIndentLevel);
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
$result .= $this->origTokens->getTokenCode(
|
||||
$pos, $commentStartPos, $indentAdjustment);
|
||||
}
|
||||
|
||||
if (!empty($delayedAdd)) {
|
||||
$result .= $this->origTokens->getTokenCode(
|
||||
$pos, $commentStartPos, $indentAdjustment);
|
||||
|
||||
/** @var Node $delayedAddNode */
|
||||
foreach ($delayedAdd as $delayedAddNode) {
|
||||
if ($insertNewline) {
|
||||
@ -795,25 +804,16 @@ abstract class PrettyPrinterAbstract
|
||||
}
|
||||
}
|
||||
|
||||
$result .= $this->origTokens->getTokenCode(
|
||||
$commentStartPos, $itemStartPos, $indentAdjustment);
|
||||
|
||||
$delayedAdd = [];
|
||||
} else if (!$skipRemovedNode) {
|
||||
$result .= $this->origTokens->getTokenCode(
|
||||
$pos, $itemStartPos, $indentAdjustment);
|
||||
} else {
|
||||
if ($isStmtList && $this->origTokens->haveBracesInRange($pos, $itemStartPos)) {
|
||||
// We'd remove the brace of a code block.
|
||||
// TODO: Preserve formatting.
|
||||
$this->setIndentLevel($origIndentLevel);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
if ($commentsChanged && $comments) {
|
||||
// Add new comments
|
||||
$result .= $this->pComments($comments) . $this->nl;
|
||||
if ($comments !== $origComments) {
|
||||
if ($comments) {
|
||||
$result .= $this->pComments($comments) . $this->nl;
|
||||
}
|
||||
} else {
|
||||
$result .= $this->origTokens->getTokenCode(
|
||||
$commentStartPos, $itemStartPos, $indentAdjustment);
|
||||
}
|
||||
|
||||
// If we had to remove anything, we have done so now.
|
||||
@ -824,7 +824,11 @@ abstract class PrettyPrinterAbstract
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($insertStr === ', ' && $this->isMultiline($origNodes)) {
|
||||
// We go multiline if the original code was multiline,
|
||||
// or if it's an array item with a comment above it.
|
||||
if ($insertStr === ', ' &&
|
||||
($this->isMultiline($origNodes) || $arrItem->getComments())
|
||||
) {
|
||||
$insertStr = ',';
|
||||
$insertNewline = true;
|
||||
}
|
||||
@ -842,11 +846,11 @@ abstract class PrettyPrinterAbstract
|
||||
$this->setIndentLevel($lastElemIndentLevel);
|
||||
|
||||
if ($insertNewline) {
|
||||
$result .= $insertStr . $this->nl;
|
||||
$comments = $arrItem->getComments();
|
||||
if ($comments) {
|
||||
$result .= $this->nl . $this->pComments($comments);
|
||||
$result .= $this->pComments($comments) . $this->nl;
|
||||
}
|
||||
$result .= $insertStr . $this->nl;
|
||||
} else {
|
||||
$result .= $insertStr;
|
||||
}
|
||||
@ -1074,7 +1078,8 @@ abstract class PrettyPrinterAbstract
|
||||
. ($modifiers & Stmt\Class_::MODIFIER_PRIVATE ? 'private ' : '')
|
||||
. ($modifiers & Stmt\Class_::MODIFIER_STATIC ? 'static ' : '')
|
||||
. ($modifiers & Stmt\Class_::MODIFIER_ABSTRACT ? 'abstract ' : '')
|
||||
. ($modifiers & Stmt\Class_::MODIFIER_FINAL ? 'final ' : '');
|
||||
. ($modifiers & Stmt\Class_::MODIFIER_FINAL ? 'final ' : '')
|
||||
. ($modifiers & Stmt\Class_::MODIFIER_READONLY ? 'readonly ' : '');
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1123,7 +1128,8 @@ abstract class PrettyPrinterAbstract
|
||||
for ($i = 0; $i < 256; $i++) {
|
||||
// Since PHP 7.1 The lower range is 0x80. However, we also want to support code for
|
||||
// older versions.
|
||||
$this->labelCharMap[chr($i)] = $i >= 0x7f || ctype_alnum($i);
|
||||
$chr = chr($i);
|
||||
$this->labelCharMap[$chr] = $i >= 0x7f || ctype_alnum($chr);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1242,7 +1248,7 @@ abstract class PrettyPrinterAbstract
|
||||
/**
|
||||
* Lazily initializes the removal map.
|
||||
*
|
||||
* The removal map is used to determine which additional tokens should be returned when a
|
||||
* The removal map is used to determine which additional tokens should be removed when a
|
||||
* certain node is replaced by null.
|
||||
*/
|
||||
protected function initializeRemovalMap() {
|
||||
@ -1269,6 +1275,8 @@ abstract class PrettyPrinterAbstract
|
||||
'Stmt_Catch->var' => $stripLeft,
|
||||
'Stmt_ClassMethod->returnType' => $stripColon,
|
||||
'Stmt_Class->extends' => ['left' => \T_EXTENDS],
|
||||
'Stmt_Enum->scalarType' => $stripColon,
|
||||
'Stmt_EnumCase->expr' => $stripEquals,
|
||||
'Expr_PrintableNewAnonClass->extends' => ['left' => \T_EXTENDS],
|
||||
'Stmt_Continue->num' => $stripBoth,
|
||||
'Stmt_Foreach->keyVar' => $stripDoubleArrow,
|
||||
@ -1307,6 +1315,8 @@ abstract class PrettyPrinterAbstract
|
||||
'Stmt_Catch->var' => [null, false, ' ', null],
|
||||
'Stmt_ClassMethod->returnType' => [')', false, ' : ', null],
|
||||
'Stmt_Class->extends' => [null, false, ' extends ', null],
|
||||
'Stmt_Enum->scalarType' => [null, false, ' : ', null],
|
||||
'Stmt_EnumCase->expr' => [null, false, ' = ', null],
|
||||
'Expr_PrintableNewAnonClass->extends' => [null, ' extends ', null],
|
||||
'Stmt_Continue->num' => [\T_CONTINUE, false, ' ', null],
|
||||
'Stmt_Foreach->keyVar' => [\T_AS, false, null, ' => '],
|
||||
@ -1337,6 +1347,7 @@ abstract class PrettyPrinterAbstract
|
||||
//'Scalar_Encapsed->parts' => '',
|
||||
'Stmt_Catch->types' => '|',
|
||||
'UnionType->types' => '|',
|
||||
'IntersectionType->types' => '&',
|
||||
'Stmt_If->elseifs' => ' ',
|
||||
'Stmt_TryCatch->catches' => ' ',
|
||||
|
||||
@ -1356,6 +1367,7 @@ abstract class PrettyPrinterAbstract
|
||||
'Stmt_ClassConst->consts' => ', ',
|
||||
'Stmt_ClassMethod->params' => ', ',
|
||||
'Stmt_Class->implements' => ', ',
|
||||
'Stmt_Enum->implements' => ', ',
|
||||
'Expr_PrintableNewAnonClass->implements' => ', ',
|
||||
'Stmt_Const->consts' => ', ',
|
||||
'Stmt_Declare->declares' => ', ',
|
||||
@ -1382,6 +1394,7 @@ abstract class PrettyPrinterAbstract
|
||||
'Stmt_Case->stmts' => "\n",
|
||||
'Stmt_Catch->stmts' => "\n",
|
||||
'Stmt_Class->stmts' => "\n",
|
||||
'Stmt_Enum->stmts' => "\n",
|
||||
'Expr_PrintableNewAnonClass->stmts' => "\n",
|
||||
'Stmt_Interface->stmts' => "\n",
|
||||
'Stmt_Trait->stmts' => "\n",
|
||||
@ -1397,6 +1410,8 @@ abstract class PrettyPrinterAbstract
|
||||
'Stmt_If->stmts' => "\n",
|
||||
'Stmt_Namespace->stmts' => "\n",
|
||||
'Stmt_Class->attrGroups' => "\n",
|
||||
'Stmt_Enum->attrGroups' => "\n",
|
||||
'Stmt_EnumCase->attrGroups' => "\n",
|
||||
'Stmt_Interface->attrGroups' => "\n",
|
||||
'Stmt_Trait->attrGroups' => "\n",
|
||||
'Stmt_Function->attrGroups' => "\n",
|
||||
@ -1435,6 +1450,7 @@ abstract class PrettyPrinterAbstract
|
||||
'Expr_PrintableNewAnonClass->implements' => [null, ' implements ', ''],
|
||||
'Expr_StaticCall->args' => ['(', '', ''],
|
||||
'Stmt_Class->implements' => [null, ' implements ', ''],
|
||||
'Stmt_Enum->implements' => [null, ' implements ', ''],
|
||||
'Stmt_ClassMethod->params' => ['(', '', ''],
|
||||
'Stmt_Interface->extends' => [null, ' extends ', ''],
|
||||
'Stmt_Function->params' => ['(', '', ''],
|
||||
|
209
test/PhpParser/Builder/ClassConstTest.php
Normal file
209
test/PhpParser/Builder/ClassConstTest.php
Normal file
@ -0,0 +1,209 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser\Builder;
|
||||
|
||||
use PhpParser\Comment;
|
||||
use PhpParser\Node\Arg;
|
||||
use PhpParser\Node\Attribute;
|
||||
use PhpParser\Node\AttributeGroup;
|
||||
use PhpParser\Node\Const_;
|
||||
use PhpParser\Node\Expr;
|
||||
use PhpParser\Node\Identifier;
|
||||
use PhpParser\Node\Name;
|
||||
use PhpParser\Node\Scalar;
|
||||
use PhpParser\Node\Scalar\LNumber;
|
||||
use PhpParser\Node\Stmt;
|
||||
|
||||
class ClassConstTest extends \PHPUnit\Framework\TestCase
|
||||
{
|
||||
public function createClassConstBuilder($name, $value) {
|
||||
return new ClassConst($name, $value);
|
||||
}
|
||||
|
||||
public function testModifiers() {
|
||||
$node = $this->createClassConstBuilder("TEST", 1)
|
||||
->makePrivate()
|
||||
->getNode()
|
||||
;
|
||||
|
||||
$this->assertEquals(
|
||||
new Stmt\ClassConst(
|
||||
[
|
||||
new Const_("TEST", new LNumber(1))
|
||||
],
|
||||
Stmt\Class_::MODIFIER_PRIVATE
|
||||
),
|
||||
$node
|
||||
);
|
||||
|
||||
$node = $this->createClassConstBuilder("TEST", 1)
|
||||
->makeProtected()
|
||||
->getNode()
|
||||
;
|
||||
|
||||
$this->assertEquals(
|
||||
new Stmt\ClassConst(
|
||||
[
|
||||
new Const_("TEST", new LNumber(1) )
|
||||
],
|
||||
Stmt\Class_::MODIFIER_PROTECTED
|
||||
),
|
||||
$node
|
||||
);
|
||||
|
||||
$node = $this->createClassConstBuilder("TEST", 1)
|
||||
->makePublic()
|
||||
->getNode()
|
||||
;
|
||||
|
||||
$this->assertEquals(
|
||||
new Stmt\ClassConst(
|
||||
[
|
||||
new Const_("TEST", new LNumber(1) )
|
||||
],
|
||||
Stmt\Class_::MODIFIER_PUBLIC
|
||||
),
|
||||
$node
|
||||
);
|
||||
|
||||
$node = $this->createClassConstBuilder("TEST", 1)
|
||||
->makeFinal()
|
||||
->getNode()
|
||||
;
|
||||
|
||||
$this->assertEquals(
|
||||
new Stmt\ClassConst(
|
||||
[
|
||||
new Const_("TEST", new LNumber(1) )
|
||||
],
|
||||
Stmt\Class_::MODIFIER_FINAL
|
||||
),
|
||||
$node
|
||||
);
|
||||
}
|
||||
|
||||
public function testDocComment() {
|
||||
$node = $this->createClassConstBuilder('TEST',1)
|
||||
->setDocComment('/** Test */')
|
||||
->makePublic()
|
||||
->getNode();
|
||||
|
||||
$this->assertEquals(
|
||||
new Stmt\ClassConst(
|
||||
[
|
||||
new Const_("TEST", new LNumber(1) )
|
||||
],
|
||||
Stmt\Class_::MODIFIER_PUBLIC,
|
||||
[
|
||||
'comments' => [new Comment\Doc('/** Test */')]
|
||||
]
|
||||
),
|
||||
$node
|
||||
);
|
||||
}
|
||||
|
||||
public function testAddConst() {
|
||||
$node = $this->createClassConstBuilder('FIRST_TEST',1)
|
||||
->addConst("SECOND_TEST",2)
|
||||
->getNode();
|
||||
|
||||
$this->assertEquals(
|
||||
new Stmt\ClassConst(
|
||||
[
|
||||
new Const_("FIRST_TEST", new LNumber(1)),
|
||||
new Const_("SECOND_TEST", new LNumber(2))
|
||||
]
|
||||
),
|
||||
$node
|
||||
);
|
||||
}
|
||||
|
||||
public function testAddAttribute() {
|
||||
$attribute = new Attribute(
|
||||
new Name('Attr'),
|
||||
[new Arg(new LNumber(1), false, false, [], new Identifier('name'))]
|
||||
);
|
||||
$attributeGroup = new AttributeGroup([$attribute]);
|
||||
|
||||
$node = $this->createClassConstBuilder('ATTR_GROUP', 1)
|
||||
->addAttribute($attributeGroup)
|
||||
->getNode();
|
||||
|
||||
$this->assertEquals(
|
||||
new Stmt\ClassConst(
|
||||
[
|
||||
new Const_("ATTR_GROUP", new LNumber(1) )
|
||||
],
|
||||
0,
|
||||
[],
|
||||
[$attributeGroup]
|
||||
),
|
||||
$node
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider provideTestDefaultValues
|
||||
*/
|
||||
public function testValues($value, $expectedValueNode) {
|
||||
$node = $this->createClassConstBuilder('TEST', $value)
|
||||
->getNode()
|
||||
;
|
||||
|
||||
$this->assertEquals($expectedValueNode, $node->consts[0]->value);
|
||||
}
|
||||
|
||||
public function provideTestDefaultValues() {
|
||||
return [
|
||||
[
|
||||
null,
|
||||
new Expr\ConstFetch(new Name('null'))
|
||||
],
|
||||
[
|
||||
true,
|
||||
new Expr\ConstFetch(new Name('true'))
|
||||
],
|
||||
[
|
||||
false,
|
||||
new Expr\ConstFetch(new Name('false'))
|
||||
],
|
||||
[
|
||||
31415,
|
||||
new Scalar\LNumber(31415)
|
||||
],
|
||||
[
|
||||
3.1415,
|
||||
new Scalar\DNumber(3.1415)
|
||||
],
|
||||
[
|
||||
'Hallo World',
|
||||
new Scalar\String_('Hallo World')
|
||||
],
|
||||
[
|
||||
[1, 2, 3],
|
||||
new Expr\Array_([
|
||||
new Expr\ArrayItem(new Scalar\LNumber(1)),
|
||||
new Expr\ArrayItem(new Scalar\LNumber(2)),
|
||||
new Expr\ArrayItem(new Scalar\LNumber(3)),
|
||||
])
|
||||
],
|
||||
[
|
||||
['foo' => 'bar', 'bar' => 'foo'],
|
||||
new Expr\Array_([
|
||||
new Expr\ArrayItem(
|
||||
new Scalar\String_('bar'),
|
||||
new Scalar\String_('foo')
|
||||
),
|
||||
new Expr\ArrayItem(
|
||||
new Scalar\String_('foo'),
|
||||
new Scalar\String_('bar')
|
||||
),
|
||||
])
|
||||
],
|
||||
[
|
||||
new Scalar\MagicConst\Dir,
|
||||
new Scalar\MagicConst\Dir
|
||||
]
|
||||
];
|
||||
}
|
||||
}
|
@ -4,7 +4,12 @@ namespace PhpParser\Builder;
|
||||
|
||||
use PhpParser\Comment;
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Arg;
|
||||
use PhpParser\Node\Attribute;
|
||||
use PhpParser\Node\AttributeGroup;
|
||||
use PhpParser\Node\Identifier;
|
||||
use PhpParser\Node\Name;
|
||||
use PhpParser\Node\Scalar\LNumber;
|
||||
use PhpParser\Node\Stmt;
|
||||
|
||||
class ClassTest extends \PHPUnit\Framework\TestCase
|
||||
@ -122,6 +127,27 @@ DOC;
|
||||
);
|
||||
}
|
||||
|
||||
public function testAddAttribute() {
|
||||
$attribute = new Attribute(
|
||||
new Name('Attr'),
|
||||
[new Arg(new LNumber(1), false, false, [], new Identifier('name'))]
|
||||
);
|
||||
$attributeGroup = new AttributeGroup([$attribute]);
|
||||
|
||||
$class = $this->createClassBuilder('ATTR_GROUP')
|
||||
->addAttribute($attributeGroup)
|
||||
->getNode();
|
||||
|
||||
$this->assertEquals(
|
||||
new Stmt\Class_('ATTR_GROUP', [
|
||||
'attrGroups' => [
|
||||
$attributeGroup,
|
||||
]
|
||||
], []),
|
||||
$class
|
||||
);
|
||||
}
|
||||
|
||||
public function testInvalidStmtError() {
|
||||
$this->expectException(\LogicException::class);
|
||||
$this->expectExceptionMessage('Unexpected node of type "Stmt_Echo"');
|
||||
|
84
test/PhpParser/Builder/EnumCaseTest.php
Normal file
84
test/PhpParser/Builder/EnumCaseTest.php
Normal file
@ -0,0 +1,84 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser\Builder;
|
||||
|
||||
use PhpParser\Comment;
|
||||
use PhpParser\Node\Arg;
|
||||
use PhpParser\Node\Attribute;
|
||||
use PhpParser\Node\AttributeGroup;
|
||||
use PhpParser\Node\Identifier;
|
||||
use PhpParser\Node\Name;
|
||||
use PhpParser\Node\Scalar;
|
||||
use PhpParser\Node\Scalar\LNumber;
|
||||
use PhpParser\Node\Stmt;
|
||||
|
||||
class EnumCaseTest extends \PHPUnit\Framework\TestCase
|
||||
{
|
||||
public function createEnumCaseBuilder($name) {
|
||||
return new EnumCase($name);
|
||||
}
|
||||
|
||||
public function testDocComment() {
|
||||
$node = $this->createEnumCaseBuilder('TEST')
|
||||
->setDocComment('/** Test */')
|
||||
->getNode();
|
||||
|
||||
$this->assertEquals(
|
||||
new Stmt\EnumCase(
|
||||
"TEST",
|
||||
null,
|
||||
[
|
||||
'comments' => [new Comment\Doc('/** Test */')]
|
||||
]
|
||||
),
|
||||
$node
|
||||
);
|
||||
}
|
||||
|
||||
public function testAddAttribute() {
|
||||
$attribute = new Attribute(
|
||||
new Name('Attr'),
|
||||
[new Arg(new LNumber(1), false, false, [], new Identifier('name'))]
|
||||
);
|
||||
$attributeGroup = new AttributeGroup([$attribute]);
|
||||
|
||||
$node = $this->createEnumCaseBuilder('ATTR_GROUP')
|
||||
->addAttribute($attributeGroup)
|
||||
->getNode();
|
||||
|
||||
$this->assertEquals(
|
||||
new Stmt\EnumCase(
|
||||
"ATTR_GROUP",
|
||||
null,
|
||||
[],
|
||||
[$attributeGroup]
|
||||
),
|
||||
$node
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider provideTestDefaultValues
|
||||
*/
|
||||
public function testValues($value, $expectedValueNode) {
|
||||
$node = $this->createEnumCaseBuilder('TEST')
|
||||
->setValue($value)
|
||||
->getNode()
|
||||
;
|
||||
|
||||
$this->assertEquals($expectedValueNode, $node->expr);
|
||||
}
|
||||
|
||||
public function provideTestDefaultValues() {
|
||||
return [
|
||||
[
|
||||
31415,
|
||||
new Scalar\LNumber(31415)
|
||||
],
|
||||
[
|
||||
'Hallo World',
|
||||
new Scalar\String_('Hallo World')
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
159
test/PhpParser/Builder/EnumTest.php
Normal file
159
test/PhpParser/Builder/EnumTest.php
Normal file
@ -0,0 +1,159 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser\Builder;
|
||||
|
||||
use PhpParser\Comment;
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Arg;
|
||||
use PhpParser\Node\Attribute;
|
||||
use PhpParser\Node\AttributeGroup;
|
||||
use PhpParser\Node\Identifier;
|
||||
use PhpParser\Node\Name;
|
||||
use PhpParser\Node\Scalar\LNumber;
|
||||
use PhpParser\Node\Stmt;
|
||||
|
||||
class EnumTest extends \PHPUnit\Framework\TestCase
|
||||
{
|
||||
protected function createEnumBuilder($class) {
|
||||
return new Enum_($class);
|
||||
}
|
||||
|
||||
public function testImplements() {
|
||||
$node = $this->createEnumBuilder('SomeEnum')
|
||||
->implement('Namespaced\SomeInterface', new Name('OtherInterface'))
|
||||
->getNode()
|
||||
;
|
||||
|
||||
$this->assertEquals(
|
||||
new Stmt\Enum_('SomeEnum', [
|
||||
'implements' => [
|
||||
new Name('Namespaced\SomeInterface'),
|
||||
new Name('OtherInterface'),
|
||||
],
|
||||
]),
|
||||
$node
|
||||
);
|
||||
}
|
||||
|
||||
public function testSetScalarType() {
|
||||
$node = $this->createEnumBuilder('Test')
|
||||
->setScalarType('int')
|
||||
->getNode()
|
||||
;
|
||||
|
||||
$this->assertEquals(
|
||||
new Stmt\Enum_('Test', [
|
||||
'scalarType' => new Identifier('int'),
|
||||
]),
|
||||
$node
|
||||
);
|
||||
}
|
||||
|
||||
public function testStatementOrder() {
|
||||
$method = new Stmt\ClassMethod('testMethod');
|
||||
$enumCase = new Stmt\EnumCase(
|
||||
'TEST_ENUM_CASE'
|
||||
);
|
||||
$const = new Stmt\ClassConst([
|
||||
new Node\Const_('TEST_CONST', new Node\Scalar\String_('ABC'))
|
||||
]);
|
||||
$use = new Stmt\TraitUse([new Name('SomeTrait')]);
|
||||
|
||||
$node = $this->createEnumBuilder('Test')
|
||||
->addStmt($method)
|
||||
->addStmt($enumCase)
|
||||
->addStmts([$const, $use])
|
||||
->getNode()
|
||||
;
|
||||
|
||||
$this->assertEquals(
|
||||
new Stmt\Enum_('Test', [
|
||||
'stmts' => [$use, $enumCase, $const, $method]
|
||||
]),
|
||||
$node
|
||||
);
|
||||
}
|
||||
|
||||
public function testDocComment() {
|
||||
$docComment = <<<'DOC'
|
||||
/**
|
||||
* Test
|
||||
*/
|
||||
DOC;
|
||||
$enum = $this->createEnumBuilder('Test')
|
||||
->setDocComment($docComment)
|
||||
->getNode();
|
||||
|
||||
$this->assertEquals(
|
||||
new Stmt\Enum_('Test', [], [
|
||||
'comments' => [
|
||||
new Comment\Doc($docComment)
|
||||
]
|
||||
]),
|
||||
$enum
|
||||
);
|
||||
|
||||
$enum = $this->createEnumBuilder('Test')
|
||||
->setDocComment(new Comment\Doc($docComment))
|
||||
->getNode();
|
||||
|
||||
$this->assertEquals(
|
||||
new Stmt\Enum_('Test', [], [
|
||||
'comments' => [
|
||||
new Comment\Doc($docComment)
|
||||
]
|
||||
]),
|
||||
$enum
|
||||
);
|
||||
}
|
||||
|
||||
public function testAddAttribute() {
|
||||
$attribute = new Attribute(
|
||||
new Name('Attr'),
|
||||
[new Arg(new LNumber(1), false, false, [], new Identifier('name'))]
|
||||
);
|
||||
$attributeGroup = new AttributeGroup([$attribute]);
|
||||
|
||||
$enum = $this->createEnumBuilder('ATTR_GROUP')
|
||||
->addAttribute($attributeGroup)
|
||||
->getNode();
|
||||
|
||||
$this->assertEquals(
|
||||
new Stmt\Enum_('ATTR_GROUP', [
|
||||
'attrGroups' => [
|
||||
$attributeGroup,
|
||||
]
|
||||
], []),
|
||||
$enum
|
||||
);
|
||||
}
|
||||
|
||||
public function testInvalidStmtError() {
|
||||
$this->expectException(\LogicException::class);
|
||||
$this->expectExceptionMessage('Unexpected node of type "Stmt_PropertyProperty"');
|
||||
$this->createEnumBuilder('Test')
|
||||
->addStmt(new Stmt\PropertyProperty('property'))
|
||||
;
|
||||
}
|
||||
|
||||
public function testInvalidDocComment() {
|
||||
$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() {
|
||||
$this->expectException(\LogicException::class);
|
||||
$this->expectExceptionMessage('Name cannot be empty');
|
||||
$this->createEnumBuilder('Test')
|
||||
->implement('');
|
||||
}
|
||||
|
||||
public function testInvalidName() {
|
||||
$this->expectException(\LogicException::class);
|
||||
$this->expectExceptionMessage('Name must be a string or an instance of Node\Name');
|
||||
$this->createEnumBuilder('Test')
|
||||
->implement(['Foo']);
|
||||
}
|
||||
}
|
@ -4,8 +4,14 @@ namespace PhpParser\Builder;
|
||||
|
||||
use PhpParser\Comment;
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Arg;
|
||||
use PhpParser\Node\Attribute;
|
||||
use PhpParser\Node\AttributeGroup;
|
||||
use PhpParser\Node\Expr\Print_;
|
||||
use PhpParser\Node\Expr\Variable;
|
||||
use PhpParser\Node\Identifier;
|
||||
use PhpParser\Node\Name;
|
||||
use PhpParser\Node\Scalar\LNumber;
|
||||
use PhpParser\Node\Scalar\String_;
|
||||
use PhpParser\Node\Stmt;
|
||||
|
||||
@ -81,6 +87,22 @@ class FunctionTest extends \PHPUnit\Framework\TestCase
|
||||
]), $node);
|
||||
}
|
||||
|
||||
public function testAddAttribute() {
|
||||
$attribute = new Attribute(
|
||||
new Name('Attr'),
|
||||
[new Arg(new LNumber(1), false, false, [], new Identifier('name'))]
|
||||
);
|
||||
$attributeGroup = new AttributeGroup([$attribute]);
|
||||
|
||||
$node = $this->createFunctionBuilder('attrGroup')
|
||||
->addAttribute($attributeGroup)
|
||||
->getNode();
|
||||
|
||||
$this->assertEquals(new Stmt\Function_('attrGroup', [
|
||||
'attrGroups' => [$attributeGroup],
|
||||
], []), $node);
|
||||
}
|
||||
|
||||
public function testReturnType() {
|
||||
$node = $this->createFunctionBuilder('test')
|
||||
->setReturnType('void')
|
||||
|
@ -4,7 +4,13 @@ namespace PhpParser\Builder;
|
||||
|
||||
use PhpParser\Comment;
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Arg;
|
||||
use PhpParser\Node\Attribute;
|
||||
use PhpParser\Node\AttributeGroup;
|
||||
use PhpParser\Node\Identifier;
|
||||
use PhpParser\Node\Name;
|
||||
use PhpParser\Node\Scalar\DNumber;
|
||||
use PhpParser\Node\Scalar\LNumber;
|
||||
use PhpParser\Node\Stmt;
|
||||
|
||||
class InterfaceTest extends \PHPUnit\Framework\TestCase
|
||||
@ -76,6 +82,22 @@ class InterfaceTest extends \PHPUnit\Framework\TestCase
|
||||
]), $node);
|
||||
}
|
||||
|
||||
public function testAddAttribute() {
|
||||
$attribute = new Attribute(
|
||||
new Name('Attr'),
|
||||
[new Arg(new LNumber(1), false, false, [], new Identifier('name'))]
|
||||
);
|
||||
$attributeGroup = new AttributeGroup([$attribute]);
|
||||
|
||||
$node = $this->createInterfaceBuilder()
|
||||
->addAttribute($attributeGroup)
|
||||
->getNode();
|
||||
|
||||
$this->assertEquals(new Stmt\Interface_('Contract', [
|
||||
'attrGroups' => [$attributeGroup],
|
||||
], []), $node);
|
||||
}
|
||||
|
||||
public function testInvalidStmtError() {
|
||||
$this->expectException(\LogicException::class);
|
||||
$this->expectExceptionMessage('Unexpected node of type "Stmt_PropertyProperty"');
|
||||
|
@ -4,8 +4,14 @@ namespace PhpParser\Builder;
|
||||
|
||||
use PhpParser\Comment;
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Arg;
|
||||
use PhpParser\Node\Attribute;
|
||||
use PhpParser\Node\AttributeGroup;
|
||||
use PhpParser\Node\Expr\Print_;
|
||||
use PhpParser\Node\Expr\Variable;
|
||||
use PhpParser\Node\Identifier;
|
||||
use PhpParser\Node\Name;
|
||||
use PhpParser\Node\Scalar\LNumber;
|
||||
use PhpParser\Node\Scalar\String_;
|
||||
use PhpParser\Node\Stmt;
|
||||
|
||||
@ -125,6 +131,22 @@ class MethodTest extends \PHPUnit\Framework\TestCase
|
||||
]), $node);
|
||||
}
|
||||
|
||||
public function testAddAttribute() {
|
||||
$attribute = new Attribute(
|
||||
new Name('Attr'),
|
||||
[new Arg(new LNumber(1), false, false, [], new Identifier('name'))]
|
||||
);
|
||||
$attributeGroup = new AttributeGroup([$attribute]);
|
||||
|
||||
$node = $this->createMethodBuilder('attributeGroup')
|
||||
->addAttribute($attributeGroup)
|
||||
->getNode();
|
||||
|
||||
$this->assertEquals(new Stmt\ClassMethod('attributeGroup', [
|
||||
'attrGroups' => [$attributeGroup],
|
||||
], []), $node);
|
||||
}
|
||||
|
||||
public function testReturnType() {
|
||||
$node = $this->createMethodBuilder('test')
|
||||
->setReturnType('bool')
|
||||
|
@ -3,8 +3,14 @@
|
||||
namespace PhpParser\Builder;
|
||||
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Arg;
|
||||
use PhpParser\Node\Attribute;
|
||||
use PhpParser\Node\AttributeGroup;
|
||||
use PhpParser\Node\Expr;
|
||||
use PhpParser\Node\Identifier;
|
||||
use PhpParser\Node\Name;
|
||||
use PhpParser\Node\Scalar;
|
||||
use PhpParser\Node\Scalar\LNumber;
|
||||
|
||||
class ParamTest extends \PHPUnit\Framework\TestCase
|
||||
{
|
||||
@ -171,7 +177,7 @@ class ParamTest extends \PHPUnit\Framework\TestCase
|
||||
|
||||
public function testInvalidTypeError() {
|
||||
$this->expectException(\LogicException::class);
|
||||
$this->expectExceptionMessage('Type must be a string, or an instance of Name, Identifier, NullableType or UnionType');
|
||||
$this->expectExceptionMessage('Type must be a string, or an instance of Name, Identifier or ComplexType');
|
||||
$this->createParamBuilder('test')->setType(new \stdClass);
|
||||
}
|
||||
|
||||
@ -198,4 +204,21 @@ class ParamTest extends \PHPUnit\Framework\TestCase
|
||||
$node
|
||||
);
|
||||
}
|
||||
|
||||
public function testAddAttribute() {
|
||||
$attribute = new Attribute(
|
||||
new Name('Attr'),
|
||||
[new Arg(new LNumber(1), false, false, [], new Identifier('name'))]
|
||||
);
|
||||
$attributeGroup = new AttributeGroup([$attribute]);
|
||||
|
||||
$node = $this->createParamBuilder('attributeGroup')
|
||||
->addAttribute($attributeGroup)
|
||||
->getNode();
|
||||
|
||||
$this->assertEquals(
|
||||
new Node\Param(new Expr\Variable('attributeGroup'), null, null, false, false, [], 0, [$attributeGroup]),
|
||||
$node
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -3,9 +3,14 @@
|
||||
namespace PhpParser\Builder;
|
||||
|
||||
use PhpParser\Comment;
|
||||
use PhpParser\Node\Arg;
|
||||
use PhpParser\Node\Attribute;
|
||||
use PhpParser\Node\AttributeGroup;
|
||||
use PhpParser\Node\Expr;
|
||||
use PhpParser\Node\Identifier;
|
||||
use PhpParser\Node\Name;
|
||||
use PhpParser\Node\Scalar;
|
||||
use PhpParser\Node\Scalar\LNumber;
|
||||
use PhpParser\Node\Stmt;
|
||||
|
||||
class PropertyTest extends \PHPUnit\Framework\TestCase
|
||||
@ -61,6 +66,21 @@ class PropertyTest extends \PHPUnit\Framework\TestCase
|
||||
),
|
||||
$node
|
||||
);
|
||||
|
||||
$node = $this->createPropertyBuilder('test')
|
||||
->makeReadonly()
|
||||
->getNode()
|
||||
;
|
||||
|
||||
$this->assertEquals(
|
||||
new Stmt\Property(
|
||||
Stmt\Class_::MODIFIER_READONLY,
|
||||
[
|
||||
new Stmt\PropertyProperty('test')
|
||||
]
|
||||
),
|
||||
$node
|
||||
);
|
||||
}
|
||||
|
||||
public function testDocComment() {
|
||||
@ -91,6 +111,32 @@ class PropertyTest extends \PHPUnit\Framework\TestCase
|
||||
$this->assertEquals($expectedValueNode, $node->props[0]->default);
|
||||
}
|
||||
|
||||
public function testAddAttribute() {
|
||||
$attribute = new Attribute(
|
||||
new Name('Attr'),
|
||||
[new Arg(new LNumber(1), false, false, [], new Identifier('name'))]
|
||||
);
|
||||
$attributeGroup = new AttributeGroup([$attribute]);
|
||||
|
||||
$node = $this->createPropertyBuilder('test')
|
||||
->addAttribute($attributeGroup)
|
||||
->getNode()
|
||||
;
|
||||
|
||||
$this->assertEquals(
|
||||
new Stmt\Property(
|
||||
Stmt\Class_::MODIFIER_PUBLIC,
|
||||
[
|
||||
new Stmt\PropertyProperty('test')
|
||||
],
|
||||
[],
|
||||
null,
|
||||
[$attributeGroup]
|
||||
),
|
||||
$node
|
||||
);
|
||||
}
|
||||
|
||||
public function provideTestDefaultValues() {
|
||||
return [
|
||||
[
|
||||
|
@ -3,7 +3,12 @@
|
||||
namespace PhpParser\Builder;
|
||||
|
||||
use PhpParser\Comment;
|
||||
use PhpParser\Node\Arg;
|
||||
use PhpParser\Node\Attribute;
|
||||
use PhpParser\Node\AttributeGroup;
|
||||
use PhpParser\Node\Identifier;
|
||||
use PhpParser\Node\Name;
|
||||
use PhpParser\Node\Scalar\LNumber;
|
||||
use PhpParser\Node\Stmt;
|
||||
use PhpParser\Node\Stmt\Class_;
|
||||
use PhpParser\Node\Stmt\ClassConst;
|
||||
@ -88,4 +93,27 @@ class TraitTest extends \PHPUnit\Framework\TestCase
|
||||
|
||||
$this->assertSame($properties, $trait->getProperties());
|
||||
}
|
||||
|
||||
public function testAddAttribute() {
|
||||
$attribute = new Attribute(
|
||||
new Name('Attr'),
|
||||
[new Arg(new LNumber(1), false, false, [], new Identifier('name'))]
|
||||
);
|
||||
$attributeGroup = new AttributeGroup([$attribute]);
|
||||
|
||||
$node = $this->createTraitBuilder('AttributeGroup')
|
||||
->addAttribute($attributeGroup)
|
||||
->getNode()
|
||||
;
|
||||
|
||||
$this->assertEquals(
|
||||
new Stmt\Trait_(
|
||||
'AttributeGroup',
|
||||
[
|
||||
'attrGroups' => [$attributeGroup],
|
||||
]
|
||||
),
|
||||
$node
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,7 @@
|
||||
namespace PhpParser;
|
||||
|
||||
use PhpParser\Node\Arg;
|
||||
use PhpParser\Node\Attribute;
|
||||
use PhpParser\Node\Expr;
|
||||
use PhpParser\Node\Expr\BinaryOp\Concat;
|
||||
use PhpParser\Node\Identifier;
|
||||
@ -26,6 +27,7 @@ class BuilderFactoryTest extends \PHPUnit\Framework\TestCase
|
||||
['class', Builder\Class_::class],
|
||||
['interface', Builder\Interface_::class],
|
||||
['trait', Builder\Trait_::class],
|
||||
['enum', Builder\Enum_::class],
|
||||
['method', Builder\Method::class],
|
||||
['function', Builder\Function_::class],
|
||||
['property', Builder\Property::class],
|
||||
@ -33,9 +35,25 @@ class BuilderFactoryTest extends \PHPUnit\Framework\TestCase
|
||||
['use', Builder\Use_::class],
|
||||
['useFunction', Builder\Use_::class],
|
||||
['useConst', Builder\Use_::class],
|
||||
['enumCase', Builder\EnumCase::class],
|
||||
];
|
||||
}
|
||||
|
||||
public function testFactoryClassConst() {
|
||||
$factory = new BuilderFactory;
|
||||
$this->assertInstanceOf(Builder\ClassConst::class, $factory->classConst('TEST',1));
|
||||
}
|
||||
|
||||
public function testAttribute() {
|
||||
$factory = new BuilderFactory();
|
||||
$this->assertEquals(
|
||||
new Attribute(new Name('AttributeName'), [new Arg(
|
||||
new String_('bar'), false, false, [], new Identifier('foo')
|
||||
)]),
|
||||
$factory->attribute('AttributeName', ['foo' => 'bar'])
|
||||
);
|
||||
}
|
||||
|
||||
public function testVal() {
|
||||
// This method is a wrapper around BuilderHelpers::normalizeValue(),
|
||||
// which is already tested elsewhere
|
||||
@ -91,6 +109,17 @@ class BuilderFactoryTest extends \PHPUnit\Framework\TestCase
|
||||
);
|
||||
}
|
||||
|
||||
public function testNamedArgs() {
|
||||
$factory = new BuilderFactory();
|
||||
$this->assertEquals(
|
||||
[
|
||||
new Arg(new String_('foo')),
|
||||
new Arg(new String_('baz'), false, false, [], new Identifier('bar')),
|
||||
],
|
||||
$factory->args(['foo', 'bar' => 'baz'])
|
||||
);
|
||||
}
|
||||
|
||||
public function testCalls() {
|
||||
$factory = new BuilderFactory();
|
||||
|
||||
@ -246,6 +275,7 @@ class BuilderFactoryTest extends \PHPUnit\Framework\TestCase
|
||||
->class('SomeClass')
|
||||
->extend('SomeOtherClass')
|
||||
->implement('A\Few', '\Interfaces')
|
||||
->addAttribute($factory->attribute('ClassAttribute', ['repository' => 'fqcn']))
|
||||
->makeAbstract()
|
||||
|
||||
->addStmt($factory->useTrait('FirstTrait'))
|
||||
@ -256,7 +286,9 @@ class BuilderFactoryTest extends \PHPUnit\Framework\TestCase
|
||||
->with($factory->traitUseAdaptation('AnotherTrait', 'baz')->as('test'))
|
||||
->with($factory->traitUseAdaptation('AnotherTrait', 'func')->insteadof('SecondTrait')))
|
||||
|
||||
->addStmt($factory->method('firstMethod'))
|
||||
->addStmt($factory->method('firstMethod')
|
||||
->addAttribute($factory->attribute('Route', ['/index', 'name' => 'homepage']))
|
||||
)
|
||||
|
||||
->addStmt($factory->method('someMethod')
|
||||
->makePublic()
|
||||
@ -270,13 +302,28 @@ class BuilderFactoryTest extends \PHPUnit\Framework\TestCase
|
||||
|
||||
->addStmt($factory->method('anotherMethod')
|
||||
->makeProtected()
|
||||
->addParam($factory->param('someParam')->setDefault('test'))
|
||||
->addParam($factory->param('someParam')
|
||||
->setDefault('test')
|
||||
->addAttribute($factory->attribute('TaggedIterator', ['app.handlers']))
|
||||
)
|
||||
->addStmt(new Expr\Print_(new Expr\Variable('someParam'))))
|
||||
|
||||
->addStmt($factory->property('someProperty')->makeProtected())
|
||||
->addStmt($factory->property('anotherProperty')
|
||||
->makePrivate()
|
||||
->setDefault([1, 2, 3])))
|
||||
->setDefault([1, 2, 3]))
|
||||
->addStmt($factory->property('integerProperty')
|
||||
->setType('int')
|
||||
->addAttribute($factory->attribute('Column', ['options' => ['unsigned' => true]]))
|
||||
->setDefault(1))
|
||||
->addStmt($factory->classConst('CONST_WITH_ATTRIBUTE', 1)
|
||||
->makePublic()
|
||||
->addAttribute($factory->attribute('ConstAttribute'))
|
||||
)
|
||||
|
||||
->addStmt($factory->classConst("FIRST_CLASS_CONST", 1)
|
||||
->addConst("SECOND_CLASS_CONST",2)
|
||||
->makePrivate()))
|
||||
->getNode()
|
||||
;
|
||||
|
||||
@ -289,6 +336,7 @@ use Foo\Bar\SomeOtherClass;
|
||||
use Foo\Bar as A;
|
||||
use function strlen;
|
||||
use const PHP_VERSION;
|
||||
#[ClassAttribute(repository: 'fqcn')]
|
||||
abstract class SomeClass extends SomeOtherClass implements A\Few, \Interfaces
|
||||
{
|
||||
use FirstTrait;
|
||||
@ -297,8 +345,14 @@ abstract class SomeClass extends SomeOtherClass implements A\Few, \Interfaces
|
||||
AnotherTrait::baz as test;
|
||||
AnotherTrait::func insteadof SecondTrait;
|
||||
}
|
||||
#[ConstAttribute]
|
||||
public const CONST_WITH_ATTRIBUTE = 1;
|
||||
private const FIRST_CLASS_CONST = 1, SECOND_CLASS_CONST = 2;
|
||||
protected $someProperty;
|
||||
private $anotherProperty = array(1, 2, 3);
|
||||
#[Column(options: array('unsigned' => true))]
|
||||
public int $integerProperty = 1;
|
||||
#[Route('/index', name: 'homepage')]
|
||||
function firstMethod()
|
||||
{
|
||||
}
|
||||
@ -308,7 +362,7 @@ abstract class SomeClass extends SomeOtherClass implements A\Few, \Interfaces
|
||||
* @param SomeClass And takes a parameter
|
||||
*/
|
||||
public abstract function someMethod(SomeClass $someParam);
|
||||
protected function anotherMethod($someParam = 'test')
|
||||
protected function anotherMethod(#[TaggedIterator('app.handlers')] $someParam = 'test')
|
||||
{
|
||||
print $someParam;
|
||||
}
|
||||
|
222
test/PhpParser/BuilderHelpersTest.php
Normal file
222
test/PhpParser/BuilderHelpersTest.php
Normal file
@ -0,0 +1,222 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser;
|
||||
|
||||
use PhpParser\Builder\Class_;
|
||||
use PhpParser\Node\Scalar;
|
||||
use PhpParser\Node\Stmt;
|
||||
use PhpParser\Node\Expr;
|
||||
|
||||
class BuilderHelpersTest extends \PHPUnit\Framework\TestCase
|
||||
{
|
||||
public function testNormalizeNode() {
|
||||
$builder = new Class_('SomeClass');
|
||||
$this->assertEquals($builder->getNode(), BuilderHelpers::normalizeNode($builder));
|
||||
|
||||
$attribute = new Node\Attribute(new Node\Name('Test'));
|
||||
$this->assertSame($attribute, BuilderHelpers::normalizeNode($attribute));
|
||||
|
||||
$this->expectException(\LogicException::class);
|
||||
$this->expectExceptionMessage('Expected node or builder object');
|
||||
BuilderHelpers::normalizeNode('test');
|
||||
}
|
||||
|
||||
public function testNormalizeStmt() {
|
||||
$stmt = new Node\Stmt\Class_('Class');
|
||||
$this->assertSame($stmt, BuilderHelpers::normalizeStmt($stmt));
|
||||
|
||||
$expr = new Expr\Variable('fn');
|
||||
$normalizedExpr = BuilderHelpers::normalizeStmt($expr);
|
||||
$this->assertEquals(new Stmt\Expression($expr), $normalizedExpr);
|
||||
$this->assertSame($expr, $normalizedExpr->expr);
|
||||
|
||||
$this->expectException(\LogicException::class);
|
||||
$this->expectExceptionMessage('Expected statement or expression node');
|
||||
BuilderHelpers::normalizeStmt(new Node\Attribute(new Node\Name('Test')));
|
||||
}
|
||||
|
||||
public function testNormalizeStmtInvalidType() {
|
||||
$this->expectException(\LogicException::class);
|
||||
$this->expectExceptionMessage('Expected node or builder object');
|
||||
BuilderHelpers::normalizeStmt('test');
|
||||
}
|
||||
|
||||
public function testNormalizeIdentifier() {
|
||||
$identifier = new Node\Identifier('fn');
|
||||
$this->assertSame($identifier, BuilderHelpers::normalizeIdentifier($identifier));
|
||||
$this->assertEquals($identifier, BuilderHelpers::normalizeIdentifier('fn'));
|
||||
|
||||
$this->expectException(\LogicException::class);
|
||||
$this->expectExceptionMessage('Expected string or instance of Node\Identifier');
|
||||
BuilderHelpers::normalizeIdentifier(1);
|
||||
}
|
||||
|
||||
public function testNormalizeIdentifierOrExpr() {
|
||||
$identifier = new Node\Identifier('fn');
|
||||
$this->assertSame($identifier, BuilderHelpers::normalizeIdentifierOrExpr($identifier));
|
||||
|
||||
$expr = new Expr\Variable('fn');
|
||||
$this->assertSame($expr, BuilderHelpers::normalizeIdentifierOrExpr($expr));
|
||||
$this->assertEquals($identifier, BuilderHelpers::normalizeIdentifierOrExpr('fn'));
|
||||
|
||||
$this->expectException(\LogicException::class);
|
||||
$this->expectExceptionMessage('Expected string or instance of Node\Identifier');
|
||||
BuilderHelpers::normalizeIdentifierOrExpr(1);
|
||||
}
|
||||
|
||||
public function testNormalizeName() {
|
||||
$name = new Node\Name('test');
|
||||
$this->assertSame($name, BuilderHelpers::normalizeName($name));
|
||||
$this->assertEquals(
|
||||
new Node\Name\FullyQualified(['Namespace', 'Test']),
|
||||
BuilderHelpers::normalizeName('\\Namespace\\Test')
|
||||
);
|
||||
$this->assertEquals(
|
||||
new Node\Name\Relative(['Test']),
|
||||
BuilderHelpers::normalizeName('namespace\\Test')
|
||||
);
|
||||
$this->assertEquals($name, BuilderHelpers::normalizeName('test'));
|
||||
|
||||
$this->expectException(\LogicException::class);
|
||||
$this->expectExceptionMessage('Name cannot be empty');
|
||||
BuilderHelpers::normalizeName('');
|
||||
}
|
||||
|
||||
public function testNormalizeNameInvalidType() {
|
||||
$this->expectException(\LogicException::class);
|
||||
$this->expectExceptionMessage('Name must be a string or an instance of Node\Name');
|
||||
BuilderHelpers::normalizeName(1);
|
||||
}
|
||||
|
||||
public function testNormalizeNameOrExpr() {
|
||||
$expr = new Expr\Variable('fn');
|
||||
$this->assertSame($expr, BuilderHelpers::normalizeNameOrExpr($expr));
|
||||
|
||||
$name = new Node\Name('test');
|
||||
$this->assertSame($name, BuilderHelpers::normalizeNameOrExpr($name));
|
||||
$this->assertEquals(
|
||||
new Node\Name\FullyQualified(['Namespace', 'Test']),
|
||||
BuilderHelpers::normalizeNameOrExpr('\\Namespace\\Test')
|
||||
);
|
||||
$this->assertEquals(
|
||||
new Node\Name\Relative(['Test']),
|
||||
BuilderHelpers::normalizeNameOrExpr('namespace\\Test')
|
||||
);
|
||||
$this->assertEquals($name, BuilderHelpers::normalizeNameOrExpr('test'));
|
||||
|
||||
$this->expectException(\LogicException::class);
|
||||
$this->expectExceptionMessage('Name cannot be empty');
|
||||
BuilderHelpers::normalizeNameOrExpr('');
|
||||
}
|
||||
|
||||
public function testNormalizeNameOrExpInvalidType() {
|
||||
$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() {
|
||||
$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'));
|
||||
$this->assertEquals(new Node\Identifier('int'), BuilderHelpers::normalizeType('int'));
|
||||
$this->assertEquals(new Node\Identifier('float'), BuilderHelpers::normalizeType('float'));
|
||||
$this->assertEquals(new Node\Identifier('bool'), BuilderHelpers::normalizeType('bool'));
|
||||
$this->assertEquals(new Node\Identifier('iterable'), BuilderHelpers::normalizeType('iterable'));
|
||||
$this->assertEquals(new Node\Identifier('void'), BuilderHelpers::normalizeType('void'));
|
||||
$this->assertEquals(new Node\Identifier('object'), BuilderHelpers::normalizeType('object'));
|
||||
$this->assertEquals(new Node\Identifier('mixed'), BuilderHelpers::normalizeType('mixed'));
|
||||
$this->assertEquals(new Node\Identifier('never'), BuilderHelpers::normalizeType('never'));
|
||||
|
||||
$intIdentifier = new Node\Identifier('int');
|
||||
$this->assertSame($intIdentifier, BuilderHelpers::normalizeType($intIdentifier));
|
||||
|
||||
$intName = new Node\Name('int');
|
||||
$this->assertSame($intName, BuilderHelpers::normalizeType($intName));
|
||||
|
||||
$intNullable = new Node\NullableType('int');
|
||||
$this->assertSame($intNullable, BuilderHelpers::normalizeType($intNullable));
|
||||
|
||||
$unionType = new Node\UnionType([new Node\Identifier('int'), new Node\Identifier('string')]);
|
||||
$this->assertSame($unionType, BuilderHelpers::normalizeType($unionType));
|
||||
|
||||
$intersectionType = new Node\IntersectionType([new Node\Name('A'), new Node\Name('B')]);
|
||||
$this->assertSame($intersectionType, BuilderHelpers::normalizeType($intersectionType));
|
||||
|
||||
$expectedNullable = new Node\NullableType($intIdentifier);
|
||||
$nullable = BuilderHelpers::normalizeType('?int');
|
||||
$this->assertEquals($expectedNullable, $nullable);
|
||||
$this->assertEquals($intIdentifier, $nullable->type);
|
||||
|
||||
$this->expectException(\LogicException::class);
|
||||
$this->expectExceptionMessage('Type must be a string, or an instance of Name, Identifier or ComplexType');
|
||||
BuilderHelpers::normalizeType(1);
|
||||
}
|
||||
|
||||
public function testNormalizeTypeNullableVoid() {
|
||||
$this->expectException(\LogicException::class);
|
||||
$this->expectExceptionMessage('void type cannot be nullable');
|
||||
BuilderHelpers::normalizeType('?void');
|
||||
}
|
||||
|
||||
public function testNormalizeTypeNullableMixed() {
|
||||
$this->expectException(\LogicException::class);
|
||||
$this->expectExceptionMessage('mixed type cannot be nullable');
|
||||
BuilderHelpers::normalizeType('?mixed');
|
||||
}
|
||||
|
||||
public function testNormalizeTypeNullableNever() {
|
||||
$this->expectException(\LogicException::class);
|
||||
$this->expectExceptionMessage('never type cannot be nullable');
|
||||
BuilderHelpers::normalizeType('?never');
|
||||
}
|
||||
|
||||
public function testNormalizeValue() {
|
||||
$expression = new Scalar\LNumber(1);
|
||||
$this->assertSame($expression, BuilderHelpers::normalizeValue($expression));
|
||||
|
||||
$this->assertEquals(new Expr\ConstFetch(new Node\Name('null')), BuilderHelpers::normalizeValue(null));
|
||||
$this->assertEquals(new Expr\ConstFetch(new Node\Name('true')), BuilderHelpers::normalizeValue(true));
|
||||
$this->assertEquals(new Expr\ConstFetch(new Node\Name('false')), BuilderHelpers::normalizeValue(false));
|
||||
$this->assertEquals(new Scalar\LNumber(2), BuilderHelpers::normalizeValue(2));
|
||||
$this->assertEquals(new Scalar\DNumber(2.5), BuilderHelpers::normalizeValue(2.5));
|
||||
$this->assertEquals(new Scalar\String_('text'), BuilderHelpers::normalizeValue('text'));
|
||||
$this->assertEquals(
|
||||
new Expr\Array_([
|
||||
new Expr\ArrayItem(new Scalar\LNumber(0)),
|
||||
new Expr\ArrayItem(new Scalar\LNumber(1), new Scalar\String_('test')),
|
||||
]),
|
||||
BuilderHelpers::normalizeValue([
|
||||
0,
|
||||
'test' => 1,
|
||||
])
|
||||
);
|
||||
|
||||
$this->expectException(\LogicException::class);
|
||||
$this->expectExceptionMessage('Invalid value');
|
||||
BuilderHelpers::normalizeValue(new \stdClass());
|
||||
}
|
||||
|
||||
public function testNormalizeDocComment() {
|
||||
$docComment = new Comment\Doc('Some doc comment');
|
||||
$this->assertSame($docComment, BuilderHelpers::normalizeDocComment($docComment));
|
||||
|
||||
$this->assertEquals($docComment, BuilderHelpers::normalizeDocComment('Some doc comment'));
|
||||
|
||||
$this->expectException(\LogicException::class);
|
||||
$this->expectExceptionMessage('Doc comment must be a string or an instance of PhpParser\Comment\Doc');
|
||||
BuilderHelpers::normalizeDocComment(1);
|
||||
}
|
||||
|
||||
public function testNormalizeAttribute() {
|
||||
$attribute = new Node\Attribute(new Node\Name('Test'));
|
||||
$attributeGroup = new Node\AttributeGroup([$attribute]);
|
||||
|
||||
$this->assertEquals($attributeGroup, BuilderHelpers::normalizeAttribute($attribute));
|
||||
$this->assertSame($attributeGroup, BuilderHelpers::normalizeAttribute($attributeGroup));
|
||||
|
||||
$this->expectException(\LogicException::class);
|
||||
$this->expectExceptionMessage('Attribute must be an instance of PhpParser\Node\Attribute or PhpParser\Node\AttributeGroup');
|
||||
BuilderHelpers::normalizeAttribute('test');
|
||||
}
|
||||
}
|
@ -107,8 +107,11 @@ class CodeParsingTest extends CodeTestAbstract
|
||||
$endFilePos < $startFilePos ||
|
||||
$endTokenPos < $startTokenPos
|
||||
) {
|
||||
// Nops and error can have inverted order, if they are empty
|
||||
if (!$node instanceof Stmt\Nop && !$node instanceof Expr\Error) {
|
||||
// Nop and Error can have inverted order, if they are empty.
|
||||
// This can also happen for a Param containing an Error.
|
||||
if (!$node instanceof Stmt\Nop && !$node instanceof Expr\Error &&
|
||||
!$node instanceof Node\Param
|
||||
) {
|
||||
throw new \Exception('End < start on ' . $node->getType());
|
||||
}
|
||||
}
|
||||
|
@ -22,6 +22,9 @@ class ConstExprEvaluatorTest extends \PHPUnit\Framework\TestCase
|
||||
['"foo"', "foo"],
|
||||
['[0, 1]', [0, 1]],
|
||||
['["foo" => "bar"]', ["foo" => "bar"]],
|
||||
['[...["bar"]]', ["bar"]],
|
||||
['[...["foo" => "bar"]]', ["foo" => "bar"]],
|
||||
['["a", "b" => "b", ...["b" => "bb", "c"]]', ["a", "b" => "bb", "c"]],
|
||||
['NULL', null],
|
||||
['False', false],
|
||||
['true', true],
|
||||
|
@ -24,6 +24,17 @@ class EmulativeTest extends LexerTest
|
||||
$this->assertSame(0, $lexer->getNextToken());
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider provideTestReplaceKeywords
|
||||
*/
|
||||
public function testReplaceKeywordsUppercase($keyword, $expectedToken) {
|
||||
$lexer = $this->getLexer();
|
||||
$lexer->startLexing('<?php ' . strtoupper($keyword));
|
||||
|
||||
$this->assertSame($expectedToken, $lexer->getNextToken());
|
||||
$this->assertSame(0, $lexer->getNextToken());
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider provideTestReplaceKeywords
|
||||
*/
|
||||
@ -302,6 +313,47 @@ class EmulativeTest extends LexerTest
|
||||
[Tokens::T_END_HEREDOC, " LABEL"],
|
||||
[ord(','), ','],
|
||||
]],
|
||||
// Enums use a contextual keyword
|
||||
['enum Foo {}', [
|
||||
[Tokens::T_ENUM, 'enum'],
|
||||
[Tokens::T_STRING, 'Foo'],
|
||||
[ord('{'), '{'],
|
||||
[ord('}'), '}'],
|
||||
]],
|
||||
['class Enum {}', [
|
||||
[Tokens::T_CLASS, 'class'],
|
||||
[Tokens::T_STRING, 'Enum'],
|
||||
[ord('{'), '{'],
|
||||
[ord('}'), '}'],
|
||||
]],
|
||||
['class Enum extends X {}', [
|
||||
[Tokens::T_CLASS, 'class'],
|
||||
[Tokens::T_STRING, 'Enum'],
|
||||
[Tokens::T_EXTENDS, 'extends'],
|
||||
[Tokens::T_STRING, 'X'],
|
||||
[ord('{'), '{'],
|
||||
[ord('}'), '}'],
|
||||
]],
|
||||
['class Enum implements X {}', [
|
||||
[Tokens::T_CLASS, 'class'],
|
||||
[Tokens::T_STRING, 'Enum'],
|
||||
[Tokens::T_IMPLEMENTS, 'implements'],
|
||||
[Tokens::T_STRING, 'X'],
|
||||
[ord('{'), '{'],
|
||||
[ord('}'), '}'],
|
||||
]],
|
||||
['0o123', [
|
||||
[Tokens::T_LNUMBER, '0o123'],
|
||||
]],
|
||||
['0O123', [
|
||||
[Tokens::T_LNUMBER, '0O123'],
|
||||
]],
|
||||
['0o1_2_3', [
|
||||
[Tokens::T_LNUMBER, '0o1_2_3'],
|
||||
]],
|
||||
['0o1000000000000000000000', [
|
||||
[Tokens::T_DNUMBER, '0o1000000000000000000000'],
|
||||
]],
|
||||
];
|
||||
}
|
||||
|
||||
@ -318,8 +370,11 @@ class EmulativeTest extends LexerTest
|
||||
return [
|
||||
['8.0', 'match', [[Tokens::T_MATCH, 'match']]],
|
||||
['7.4', 'match', [[Tokens::T_STRING, 'match']]],
|
||||
// Keywords are not case-sensitive.
|
||||
['7.4', 'fn', [[Tokens::T_FN, 'fn']]],
|
||||
['7.4', 'FN', [[Tokens::T_FN, 'FN']]],
|
||||
['7.3', 'fn', [[Tokens::T_STRING, 'fn']]],
|
||||
['7.3', 'FN', [[Tokens::T_STRING, 'FN']]],
|
||||
// Tested here to skip testLeaveStuffAloneInStrings.
|
||||
['8.0', '"$foo?->bar"', [
|
||||
[ord('"'), '"'],
|
||||
|
38
test/PhpParser/Node/Expr/CallableLikeTest.php
Normal file
38
test/PhpParser/Node/Expr/CallableLikeTest.php
Normal file
@ -0,0 +1,38 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser\Node\Expr;
|
||||
|
||||
use PhpParser\Node\Arg;
|
||||
use PhpParser\Node\Name;
|
||||
use PhpParser\Node\Scalar\LNumber;
|
||||
use PhpParser\Node\VariadicPlaceholder;
|
||||
|
||||
class CallableLikeTest extends \PHPUnit\Framework\TestCase {
|
||||
/**
|
||||
* @dataProvider provideTestIsFirstClassCallable
|
||||
*/
|
||||
public function testIsFirstClassCallable(CallLike $node, bool $isFirstClassCallable) {
|
||||
$this->assertSame($isFirstClassCallable, $node->isFirstClassCallable());
|
||||
if (!$isFirstClassCallable) {
|
||||
$this->assertSame($node->getRawArgs(), $node->getArgs());
|
||||
}
|
||||
}
|
||||
|
||||
public function provideTestIsFirstClassCallable() {
|
||||
$normalArgs = [new Arg(new LNumber(1))];
|
||||
$callableArgs = [new VariadicPlaceholder()];
|
||||
return [
|
||||
[new FuncCall(new Name('test'), $normalArgs), false],
|
||||
[new FuncCall(new Name('test'), $callableArgs), true],
|
||||
[new MethodCall(new Variable('this'), 'test', $normalArgs), false],
|
||||
[new MethodCall(new Variable('this'), 'test', $callableArgs), true],
|
||||
[new StaticCall(new Name('Test'), 'test', $normalArgs), false],
|
||||
[new StaticCall(new Name('Test'), 'test', $callableArgs), true],
|
||||
[new New_(new Name('Test'), $normalArgs), false],
|
||||
[new NullsafeMethodCall(new Variable('this'), 'test', $normalArgs), false],
|
||||
// This is not legal code, but accepted by the parser.
|
||||
[new New_(new Name('Test'), $callableArgs), true],
|
||||
[new NullsafeMethodCall(new Variable('this'), 'test', $callableArgs), true],
|
||||
];
|
||||
}
|
||||
}
|
@ -22,6 +22,7 @@ class ClassConstTest extends \PHPUnit\Framework\TestCase
|
||||
$this->assertTrue($node->isPublic());
|
||||
$this->assertFalse($node->isProtected());
|
||||
$this->assertFalse($node->isPrivate());
|
||||
$this->assertFalse($node->isFinal());
|
||||
}
|
||||
|
||||
public function provideModifiers() {
|
||||
@ -29,6 +30,7 @@ class ClassConstTest extends \PHPUnit\Framework\TestCase
|
||||
['public'],
|
||||
['protected'],
|
||||
['private'],
|
||||
['final'],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -23,6 +23,7 @@ class PropertyTest extends \PHPUnit\Framework\TestCase
|
||||
$this->assertFalse($node->isProtected());
|
||||
$this->assertFalse($node->isPrivate());
|
||||
$this->assertFalse($node->isStatic());
|
||||
$this->assertFalse($node->isReadonly());
|
||||
}
|
||||
|
||||
public function testStaticImplicitlyPublic() {
|
||||
@ -31,6 +32,12 @@ class PropertyTest extends \PHPUnit\Framework\TestCase
|
||||
$this->assertFalse($node->isProtected());
|
||||
$this->assertFalse($node->isPrivate());
|
||||
$this->assertTrue($node->isStatic());
|
||||
$this->assertFalse($node->isReadonly());
|
||||
}
|
||||
|
||||
public function testReadonly() {
|
||||
$node = new Property(Class_::MODIFIER_READONLY, []);
|
||||
$this->assertTrue($node->isReadonly());
|
||||
}
|
||||
|
||||
public function provideModifiers() {
|
||||
|
@ -6,11 +6,13 @@ class DummyNode extends NodeAbstract
|
||||
{
|
||||
public $subNode1;
|
||||
public $subNode2;
|
||||
public $notSubNode;
|
||||
|
||||
public function __construct($subNode1, $subNode2, $attributes) {
|
||||
public function __construct($subNode1, $subNode2, $notSubNode, $attributes) {
|
||||
parent::__construct($attributes);
|
||||
$this->subNode1 = $subNode1;
|
||||
$this->subNode2 = $subNode2;
|
||||
$this->notSubNode = $notSubNode;
|
||||
}
|
||||
|
||||
public function getSubNodeNames() : array {
|
||||
@ -40,8 +42,7 @@ class NodeAbstractTest extends \PHPUnit\Framework\TestCase
|
||||
],
|
||||
];
|
||||
|
||||
$node = new DummyNode('value1', 'value2', $attributes);
|
||||
$node->notSubNode = 'value3';
|
||||
$node = new DummyNode('value1', 'value2', 'value3', $attributes);
|
||||
|
||||
return [
|
||||
[$attributes, $node],
|
||||
@ -90,7 +91,7 @@ class NodeAbstractTest extends \PHPUnit\Framework\TestCase
|
||||
}
|
||||
|
||||
public function testSetDocComment() {
|
||||
$node = new DummyNode(null, null, []);
|
||||
$node = new DummyNode(null, null, null, []);
|
||||
|
||||
// Add doc comment to node without comments
|
||||
$docComment = new Comment\Doc('/** doc */');
|
||||
@ -120,19 +121,19 @@ class NodeAbstractTest extends \PHPUnit\Framework\TestCase
|
||||
/**
|
||||
* @dataProvider provideNodes
|
||||
*/
|
||||
public function testChange(array $attributes, Node $node) {
|
||||
public function testChange(array $attributes, DummyNode $node) {
|
||||
// direct modification
|
||||
$node->subNode = 'newValue';
|
||||
$this->assertSame('newValue', $node->subNode);
|
||||
$node->subNode1 = 'newValue';
|
||||
$this->assertSame('newValue', $node->subNode1);
|
||||
|
||||
// indirect modification
|
||||
$subNode =& $node->subNode;
|
||||
$subNode =& $node->subNode1;
|
||||
$subNode = 'newNewValue';
|
||||
$this->assertSame('newNewValue', $node->subNode);
|
||||
$this->assertSame('newNewValue', $node->subNode1);
|
||||
|
||||
// removal
|
||||
unset($node->subNode);
|
||||
$this->assertObjectNotHasAttribute('subNode', $node);
|
||||
unset($node->subNode1);
|
||||
$this->assertFalse(isset($node->subNode1));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -305,6 +306,7 @@ PHP;
|
||||
}
|
||||
],
|
||||
"attrGroups": [],
|
||||
"namespacedName": null,
|
||||
"attributes": {
|
||||
"startLine": 4,
|
||||
"comments": [
|
||||
@ -334,6 +336,134 @@ PHP;
|
||||
}
|
||||
]
|
||||
JSON;
|
||||
$expected81 = <<<'JSON'
|
||||
[
|
||||
{
|
||||
"nodeType": "Stmt_Function",
|
||||
"attributes": {
|
||||
"startLine": 4,
|
||||
"comments": [
|
||||
{
|
||||
"nodeType": "Comment",
|
||||
"text": "\/\/ comment",
|
||||
"line": 2,
|
||||
"filePos": 6,
|
||||
"tokenPos": 1,
|
||||
"endLine": 2,
|
||||
"endFilePos": 15,
|
||||
"endTokenPos": 1
|
||||
},
|
||||
{
|
||||
"nodeType": "Comment_Doc",
|
||||
"text": "\/** doc comment *\/",
|
||||
"line": 3,
|
||||
"filePos": 17,
|
||||
"tokenPos": 3,
|
||||
"endLine": 3,
|
||||
"endFilePos": 34,
|
||||
"endTokenPos": 3
|
||||
}
|
||||
],
|
||||
"endLine": 6
|
||||
},
|
||||
"byRef": false,
|
||||
"name": {
|
||||
"nodeType": "Identifier",
|
||||
"attributes": {
|
||||
"startLine": 4,
|
||||
"endLine": 4
|
||||
},
|
||||
"name": "functionName"
|
||||
},
|
||||
"params": [
|
||||
{
|
||||
"nodeType": "Param",
|
||||
"attributes": {
|
||||
"startLine": 4,
|
||||
"endLine": 4
|
||||
},
|
||||
"type": null,
|
||||
"byRef": true,
|
||||
"variadic": false,
|
||||
"var": {
|
||||
"nodeType": "Expr_Variable",
|
||||
"attributes": {
|
||||
"startLine": 4,
|
||||
"endLine": 4
|
||||
},
|
||||
"name": "a"
|
||||
},
|
||||
"default": {
|
||||
"nodeType": "Scalar_LNumber",
|
||||
"attributes": {
|
||||
"startLine": 4,
|
||||
"endLine": 4,
|
||||
"kind": 10
|
||||
},
|
||||
"value": 0
|
||||
},
|
||||
"flags": 0,
|
||||
"attrGroups": []
|
||||
},
|
||||
{
|
||||
"nodeType": "Param",
|
||||
"attributes": {
|
||||
"startLine": 4,
|
||||
"endLine": 4
|
||||
},
|
||||
"type": null,
|
||||
"byRef": false,
|
||||
"variadic": false,
|
||||
"var": {
|
||||
"nodeType": "Expr_Variable",
|
||||
"attributes": {
|
||||
"startLine": 4,
|
||||
"endLine": 4
|
||||
},
|
||||
"name": "b"
|
||||
},
|
||||
"default": {
|
||||
"nodeType": "Scalar_DNumber",
|
||||
"attributes": {
|
||||
"startLine": 4,
|
||||
"endLine": 4
|
||||
},
|
||||
"value": 1
|
||||
},
|
||||
"flags": 0,
|
||||
"attrGroups": []
|
||||
}
|
||||
],
|
||||
"returnType": null,
|
||||
"stmts": [
|
||||
{
|
||||
"nodeType": "Stmt_Echo",
|
||||
"attributes": {
|
||||
"startLine": 5,
|
||||
"endLine": 5
|
||||
},
|
||||
"exprs": [
|
||||
{
|
||||
"nodeType": "Scalar_String",
|
||||
"attributes": {
|
||||
"startLine": 5,
|
||||
"endLine": 5,
|
||||
"kind": 1
|
||||
},
|
||||
"value": "Foo"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"attrGroups": [],
|
||||
"namespacedName": null
|
||||
}
|
||||
]
|
||||
JSON;
|
||||
|
||||
if (version_compare(PHP_VERSION, '8.1', '>=')) {
|
||||
$expected = $expected81;
|
||||
}
|
||||
|
||||
$parser = new Parser\Php7(new Lexer());
|
||||
$stmts = $parser->parse(canonicalize($code));
|
||||
|
@ -204,6 +204,13 @@ class A extends B implements C, D {
|
||||
interface A extends C, D {
|
||||
public function a(A $a) : A;
|
||||
public function b(A|B|int $a): A|B|int;
|
||||
public function c(A&B $a): A&B;
|
||||
}
|
||||
|
||||
#[X]
|
||||
enum E: int {
|
||||
#[X]
|
||||
case A = 1;
|
||||
}
|
||||
|
||||
#[X]
|
||||
@ -262,6 +269,13 @@ interface A extends \NS\C, \NS\D
|
||||
{
|
||||
public function a(\NS\A $a) : \NS\A;
|
||||
public function b(\NS\A|\NS\B|int $a) : \NS\A|\NS\B|int;
|
||||
public function c(\NS\A&\NS\B $a) : \NS\A&\NS\B;
|
||||
}
|
||||
#[\NS\X]
|
||||
enum E : int
|
||||
{
|
||||
#[\NS\X]
|
||||
case A = 1;
|
||||
}
|
||||
#[\NS\X]
|
||||
trait A
|
||||
@ -327,6 +341,7 @@ EOC;
|
||||
]),
|
||||
new Stmt\Trait_('E'),
|
||||
new Expr\New_(new Stmt\Class_(null)),
|
||||
new Stmt\Enum_('F'),
|
||||
];
|
||||
|
||||
$traverser = new PhpParser\NodeTraverser;
|
||||
@ -338,7 +353,8 @@ EOC;
|
||||
$this->assertSame('NS\\C', (string) $stmts[0]->stmts[2]->namespacedName);
|
||||
$this->assertSame('NS\\D', (string) $stmts[0]->stmts[3]->consts[0]->namespacedName);
|
||||
$this->assertSame('NS\\E', (string) $stmts[0]->stmts[4]->namespacedName);
|
||||
$this->assertObjectNotHasAttribute('namespacedName', $stmts[0]->stmts[5]->class);
|
||||
$this->assertNull($stmts[0]->stmts[5]->class->namespacedName);
|
||||
$this->assertSame('NS\\F', (string) $stmts[0]->stmts[6]->namespacedName);
|
||||
|
||||
$stmts = $traverser->traverse([new Stmt\Namespace_(null, $nsStmts)]);
|
||||
$this->assertSame('A', (string) $stmts[0]->stmts[0]->namespacedName);
|
||||
@ -346,7 +362,8 @@ EOC;
|
||||
$this->assertSame('C', (string) $stmts[0]->stmts[2]->namespacedName);
|
||||
$this->assertSame('D', (string) $stmts[0]->stmts[3]->consts[0]->namespacedName);
|
||||
$this->assertSame('E', (string) $stmts[0]->stmts[4]->namespacedName);
|
||||
$this->assertObjectNotHasAttribute('namespacedName', $stmts[0]->stmts[5]->class);
|
||||
$this->assertNull($stmts[0]->stmts[5]->class->namespacedName);
|
||||
$this->assertSame('F', (string) $stmts[0]->stmts[6]->namespacedName);
|
||||
}
|
||||
|
||||
public function testAddRuntimeResolvedNamespacedName() {
|
||||
|
@ -135,6 +135,8 @@ EOC;
|
||||
['0XF', ['kind' => Scalar\LNumber::KIND_HEX]],
|
||||
['0b1', ['kind' => Scalar\LNumber::KIND_BIN]],
|
||||
['0B1', ['kind' => Scalar\LNumber::KIND_BIN]],
|
||||
['0o7', ['kind' => Scalar\LNumber::KIND_OCT]],
|
||||
['0O7', ['kind' => Scalar\LNumber::KIND_OCT]],
|
||||
['[]', ['kind' => Expr\Array_::KIND_SHORT]],
|
||||
['array()', ['kind' => Expr\Array_::KIND_LONG]],
|
||||
["'foo'", ['kind' => String_::KIND_SINGLE_QUOTED]],
|
||||
|
106
test/code/formatPreservation/arrayInsertionWithComments.test
Normal file
106
test/code/formatPreservation/arrayInsertionWithComments.test
Normal file
@ -0,0 +1,106 @@
|
||||
Inserting array item with comment
|
||||
-----
|
||||
<?php
|
||||
|
||||
$items = [
|
||||
'a' => 'foo',
|
||||
'b' => 'bar',
|
||||
];
|
||||
-----
|
||||
$node = new Expr\ArrayItem(new Scalar\String_('baz'), new Scalar\String_('c'));
|
||||
$node->setAttribute('comments', [new Comment\Doc(<<<COMMENT
|
||||
/**
|
||||
* A doc comment
|
||||
*/
|
||||
COMMENT
|
||||
)]);
|
||||
$array = $stmts[0]->expr->expr;
|
||||
$array->items[] = $node;
|
||||
-----
|
||||
<?php
|
||||
|
||||
$items = [
|
||||
'a' => 'foo',
|
||||
'b' => 'bar',
|
||||
/**
|
||||
* A doc comment
|
||||
*/
|
||||
'c' => 'baz',
|
||||
];
|
||||
-----
|
||||
<?php
|
||||
|
||||
$items = [
|
||||
'a' => 'foo',
|
||||
'b' => 'bar',
|
||||
];
|
||||
-----
|
||||
$node = new Expr\ArrayItem(new Scalar\String_('baz'), new Scalar\String_('c'));
|
||||
$node->setAttribute('comments', [new Comment("/* Block comment */")]);
|
||||
$array = $stmts[0]->expr->expr;
|
||||
$array->items[] = $node;
|
||||
-----
|
||||
<?php
|
||||
|
||||
$items = [
|
||||
'a' => 'foo',
|
||||
'b' => 'bar',
|
||||
/* Block comment */
|
||||
'c' => 'baz',
|
||||
];
|
||||
-----
|
||||
<?php
|
||||
|
||||
$items = [
|
||||
'a' => 'foo',
|
||||
'b' => 'bar',
|
||||
];
|
||||
-----
|
||||
$node = new Expr\ArrayItem(new Scalar\String_('baz'), new Scalar\String_('c'));
|
||||
$node->setAttribute('comments', [new Comment("// Line comment")]);
|
||||
$array = $stmts[0]->expr->expr;
|
||||
$array->items[] = $node;
|
||||
-----
|
||||
<?php
|
||||
|
||||
$items = [
|
||||
'a' => 'foo',
|
||||
'b' => 'bar',
|
||||
// Line comment
|
||||
'c' => 'baz',
|
||||
];
|
||||
-----
|
||||
<?php
|
||||
|
||||
$items = [
|
||||
'a' => 'foo',
|
||||
];
|
||||
-----
|
||||
$node = new Expr\ArrayItem(new Scalar\String_('bar'), new Scalar\String_('b'));
|
||||
$node->setAttribute('comments', [new Comment("// Line comment")]);
|
||||
$array = $stmts[0]->expr->expr;
|
||||
$array->items[] = $node;
|
||||
-----
|
||||
<?php
|
||||
|
||||
$items = [
|
||||
'a' => 'foo',
|
||||
// Line comment
|
||||
'b' => 'bar',
|
||||
];
|
||||
-----
|
||||
<?php
|
||||
|
||||
$items = [];
|
||||
-----
|
||||
$node = new Expr\ArrayItem(new Scalar\String_('foo'), new Scalar\String_('a'));
|
||||
$node->setAttribute('comments', [new Comment("// Line comment")]);
|
||||
$array = $stmts[0]->expr->expr;
|
||||
$array->items[] = $node;
|
||||
-----
|
||||
<?php
|
||||
|
||||
$items = [
|
||||
// Line comment
|
||||
'a' => 'foo',
|
||||
];
|
100
test/code/formatPreservation/enum.test
Normal file
100
test/code/formatPreservation/enum.test
Normal file
@ -0,0 +1,100 @@
|
||||
Enum formatting preservation
|
||||
-----
|
||||
<?php
|
||||
enum X: int
|
||||
{}
|
||||
-----
|
||||
$stmts[0]->scalarType = null;
|
||||
-----
|
||||
<?php
|
||||
enum X
|
||||
{}
|
||||
-----
|
||||
<?php
|
||||
enum X {
|
||||
case
|
||||
Y = 1;
|
||||
}
|
||||
-----
|
||||
$stmts[0]->stmts[0]->expr = null;
|
||||
-----
|
||||
<?php
|
||||
enum X {
|
||||
case
|
||||
Y;
|
||||
}
|
||||
-----
|
||||
<?php
|
||||
enum X
|
||||
{}
|
||||
-----
|
||||
$stmts[0]->scalarType = new Node\Identifier('int');
|
||||
-----
|
||||
<?php
|
||||
enum X : int
|
||||
{}
|
||||
-----
|
||||
<?php
|
||||
enum X
|
||||
implements Y
|
||||
{}
|
||||
-----
|
||||
$stmts[0]->scalarType = new Node\Identifier('int');
|
||||
-----
|
||||
<?php
|
||||
enum X : int
|
||||
implements Y
|
||||
{}
|
||||
-----
|
||||
<?php
|
||||
enum X {
|
||||
case
|
||||
Y;
|
||||
}
|
||||
-----
|
||||
$stmts[0]->stmts[0]->expr = new Scalar\LNumber(1);
|
||||
-----
|
||||
<?php
|
||||
enum X {
|
||||
case
|
||||
Y = 1;
|
||||
}
|
||||
-----
|
||||
<?php
|
||||
enum X {
|
||||
case A;
|
||||
|
||||
case B;
|
||||
}
|
||||
-----
|
||||
$stmts[0]->stmts[] = new Node\Stmt\EnumCase('C');
|
||||
-----
|
||||
<?php
|
||||
enum X {
|
||||
case A;
|
||||
|
||||
case B;
|
||||
case C;
|
||||
}
|
||||
-----
|
||||
<?php
|
||||
enum X
|
||||
implements Y
|
||||
{}
|
||||
-----
|
||||
$stmts[0]->implements[] = new Node\Name('Z');
|
||||
-----
|
||||
<?php
|
||||
enum X
|
||||
implements Y, Z
|
||||
{}
|
||||
-----
|
||||
<?php
|
||||
enum X
|
||||
{}
|
||||
-----
|
||||
$stmts[0]->implements[] = new Node\Name('Y');
|
||||
-----
|
||||
<?php
|
||||
enum X implements Y
|
||||
{}
|
@ -316,4 +316,33 @@ $stmts[0]->returnType->types[] = new Node\Name('C');
|
||||
-----
|
||||
<?php
|
||||
function test(): A
|
||||
|B|C {}
|
||||
|B|C {}
|
||||
-----
|
||||
<?php
|
||||
function test(): A
|
||||
&B {}
|
||||
-----
|
||||
$stmts[0]->returnType->types[] = new Node\Name('C');
|
||||
-----
|
||||
<?php
|
||||
function test(): A
|
||||
&B&C {}
|
||||
-----
|
||||
<?php
|
||||
function test() {
|
||||
if ($x) {
|
||||
$a;
|
||||
$b;
|
||||
}
|
||||
$z;
|
||||
}
|
||||
-----
|
||||
$fnStmts =& $stmts[0]->stmts;
|
||||
array_splice($fnStmts, 0, 1, $fnStmts[0]->stmts);
|
||||
-----
|
||||
<?php
|
||||
function test() {
|
||||
$a;
|
||||
$b;
|
||||
$z;
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user