mirror of
https://github.com/nikic/PHP-Parser.git
synced 2025-07-22 23:01:26 +02:00
Compare commits
59 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
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 |
.editorconfig.gitattributes
.github/workflows
.travis.ymlCHANGELOG.mdREADME.mdbin
doc
grammar
lib/PhpParser
Builder
ClassConst.phpClass_.phpFunction_.phpInterface_.phpMethod.phpNamespace_.phpParam.phpProperty.phpTrait_.phpUse_.php
BuilderFactory.phpBuilderHelpers.phpInternal
Lexer.phpLexer
Node
NodeDumper.phpNodeVisitor
Parser
ParserAbstract.phpPrettyPrinter
PrettyPrinterAbstract.phptest
PhpParser
Builder
ClassConstTest.phpClassTest.phpFunctionTest.phpInterfaceTest.phpMethodTest.phpParamTest.phpPropertyTest.phpTraitTest.php
BuilderFactoryTest.phpBuilderHelpersTest.phpCodeParsingTest.phpLexer
Node
NodeAbstractTest.phpNodeVisitor
code
formatPreservation
parser
errorHandling
expr
stmt
prettyPrinter
test_old
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
|
16
.gitattributes
vendored
16
.gitattributes
vendored
@@ -1,9 +1,9 @@
|
||||
/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
|
||||
.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
|
92
CHANGELOG.md
92
CHANGELOG.md
@@ -1,8 +1,98 @@
|
||||
Version 4.10.1-dev
|
||||
Version 4.12.1-dev
|
||||
------------------
|
||||
|
||||
Nothing yet.
|
||||
|
||||
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.
|
||||
|
@@ -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]; }
|
||||
;
|
||||
|
||||
|
@@ -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,12 +33,12 @@ 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:
|
||||
@@ -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:
|
||||
@@ -348,10 +358,7 @@ function_declaration_statement:
|
||||
;
|
||||
|
||||
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 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 '}'
|
||||
@@ -359,6 +366,18 @@ class_declaration_statement:
|
||||
$this->checkInterface($$, #3); }
|
||||
| optional_attributes T_TRAIT identifier '{' class_statement_list '}'
|
||||
{ $$ = Stmt\Trait_[$3, ['stmts' => $5, 'attrGroups' => $1]]; }
|
||||
| optional_attributes T_ENUM identifier 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:
|
||||
@@ -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); }
|
||||
;
|
||||
@@ -516,16 +535,20 @@ optional_visibility_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_visibility_modifier 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_visibility_modifier 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_visibility_modifier optional_type_without_static
|
||||
optional_arg_ref optional_ellipsis error
|
||||
{ $$ = new Node\Param(Expr\Error[], null, $3, $4, $5, attributes(), $2, $1); }
|
||||
;
|
||||
|
||||
@@ -585,7 +608,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]; }
|
||||
| identifier_ex ':' expr
|
||||
{ $$ = new Node\Arg($3, false, false, attributes(), $1); }
|
||||
@@ -601,7 +624,7 @@ non_empty_global_var_list:
|
||||
;
|
||||
|
||||
global_var:
|
||||
simple_variable { $$ = Expr\Variable[$1]; }
|
||||
simple_variable { $$ = $1; }
|
||||
;
|
||||
|
||||
static_var_list:
|
||||
@@ -630,16 +653,18 @@ 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
|
||||
{ $$ = 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 enum_case_expr semi
|
||||
{ $$ = Stmt\EnumCase[$3, $4, $1]; }
|
||||
| error { $$ = null; /* will be skipped */ }
|
||||
;
|
||||
|
||||
@@ -701,6 +726,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 +771,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 +798,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 +859,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]]; }
|
||||
@@ -1007,7 +1034,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 +1059,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 +1076,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]; }
|
||||
@@ -1063,13 +1090,13 @@ new_variable:
|
||||
member_name:
|
||||
identifier_ex { $$ = $1; }
|
||||
| '{' expr '}' { $$ = $2; }
|
||||
| simple_variable { $$ = Expr\Variable[$1]; }
|
||||
| simple_variable { $$ = $1; }
|
||||
;
|
||||
|
||||
property_name:
|
||||
identifier { $$ = $1; }
|
||||
| '{' expr '}' { $$ = $2; }
|
||||
| simple_variable { $$ = Expr\Variable[$1]; }
|
||||
| simple_variable { $$ = $1; }
|
||||
| error { $$ = Expr\Error[]; $this->errorState = 2; }
|
||||
;
|
||||
|
||||
@@ -1095,10 +1122,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; }
|
||||
|
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);
|
||||
}
|
||||
}
|
||||
|
@@ -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.
|
||||
*
|
||||
@@ -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,6 +4,7 @@ namespace PhpParser\Builder;
|
||||
|
||||
use PhpParser;
|
||||
use PhpParser\BuilderHelpers;
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Identifier;
|
||||
use PhpParser\Node\Name;
|
||||
use PhpParser\Node\NullableType;
|
||||
@@ -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.
|
||||
*
|
||||
@@ -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.
|
||||
*
|
||||
@@ -161,6 +176,18 @@ 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 node a for a literal value.
|
||||
*
|
||||
@@ -198,12 +225,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;
|
||||
}
|
||||
|
@@ -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;
|
||||
}
|
||||
|
||||
@@ -127,18 +129,22 @@ final class BuilderHelpers
|
||||
private static function normalizeNameCommon($name, bool $allowExpr) {
|
||||
if ($name instanceof Name) {
|
||||
return $name;
|
||||
} elseif (is_string($name)) {
|
||||
}
|
||||
|
||||
if (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 (0 === strpos($name, 'namespace\\')) {
|
||||
return new Name\Relative(substr($name, strlen('namespace\\')));
|
||||
}
|
||||
|
||||
return new Name($name);
|
||||
}
|
||||
|
||||
if ($allowExpr) {
|
||||
@@ -148,9 +154,9 @@ final class BuilderHelpers
|
||||
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');
|
||||
}
|
||||
|
||||
throw new \LogicException('Name must be a string or an instance of Node\Name');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -183,7 +189,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 +199,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 +220,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 +265,9 @@ final class BuilderHelpers
|
||||
}
|
||||
|
||||
return new Expr\Array_($items);
|
||||
} else {
|
||||
throw new \LogicException('Invalid value');
|
||||
}
|
||||
|
||||
throw new \LogicException('Invalid value');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -263,11 +280,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]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -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,23 @@ 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\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 = [];
|
||||
@@ -38,7 +40,7 @@ class Emulative extends Lexer
|
||||
*/
|
||||
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 +53,8 @@ class Emulative extends Lexer
|
||||
new NumericLiteralSeparatorEmulator(),
|
||||
new NullsafeTokenEmulator(),
|
||||
new AttributeEmulator(),
|
||||
new EnumTokenEmulator(),
|
||||
new ReadonlyTokenEmulator(),
|
||||
];
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
@@ -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;
|
||||
}
|
||||
}
|
@@ -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;
|
||||
|
@@ -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';
|
||||
}
|
||||
|
@@ -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';
|
||||
}
|
||||
}
|
@@ -77,6 +77,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';
|
||||
}
|
||||
|
@@ -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
|
||||
|
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)));
|
||||
}
|
||||
}
|
||||
|
@@ -727,6 +727,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 +745,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 +999,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 +1068,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 +1077,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 +1085,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.
|
||||
@@ -1074,7 +1074,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 +1124,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 +1244,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 +1271,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 +1311,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, ' => '],
|
||||
@@ -1356,6 +1362,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 +1389,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 +1405,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 +1445,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"');
|
||||
|
@@ -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
|
||||
{
|
||||
@@ -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;
|
||||
@@ -36,6 +37,21 @@ class BuilderFactoryTest extends \PHPUnit\Framework\TestCase
|
||||
];
|
||||
}
|
||||
|
||||
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 +107,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 +273,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 +284,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 +300,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 +334,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 +343,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 +360,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;
|
||||
}
|
||||
|
219
test/PhpParser/BuilderHelpersTest.php
Normal file
219
test/PhpParser/BuilderHelpersTest.php
Normal file
@@ -0,0 +1,219 @@
|
||||
<?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));
|
||||
|
||||
$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, NullableType or UnionType');
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
@@ -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,35 @@ 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('}'), '}'],
|
||||
]],
|
||||
];
|
||||
}
|
||||
|
||||
@@ -318,8 +358,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('"'), '"'],
|
||||
|
@@ -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() {
|
||||
|
@@ -334,6 +334,133 @@ 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": []
|
||||
}
|
||||
]
|
||||
JSON;
|
||||
|
||||
if (version_compare(PHP_VERSION, '8.1', '>=')) {
|
||||
$expected = $expected81;
|
||||
}
|
||||
|
||||
$parser = new Parser\Php7(new Lexer());
|
||||
$stmts = $parser->parse(canonicalize($code));
|
||||
|
@@ -206,6 +206,12 @@ interface A extends C, D {
|
||||
public function b(A|B|int $a): A|B|int;
|
||||
}
|
||||
|
||||
#[X]
|
||||
enum E: int {
|
||||
#[X]
|
||||
case A = 1;
|
||||
}
|
||||
|
||||
#[X]
|
||||
trait A {}
|
||||
|
||||
@@ -264,6 +270,12 @@ interface A extends \NS\C, \NS\D
|
||||
public function b(\NS\A|\NS\B|int $a) : \NS\A|\NS\B|int;
|
||||
}
|
||||
#[\NS\X]
|
||||
enum E : int
|
||||
{
|
||||
#[\NS\X]
|
||||
case A = 1;
|
||||
}
|
||||
#[\NS\X]
|
||||
trait A
|
||||
{
|
||||
}
|
||||
@@ -327,6 +339,7 @@ EOC;
|
||||
]),
|
||||
new Stmt\Trait_('E'),
|
||||
new Expr\New_(new Stmt\Class_(null)),
|
||||
new Stmt\Enum_('F'),
|
||||
];
|
||||
|
||||
$traverser = new PhpParser\NodeTraverser;
|
||||
@@ -339,6 +352,7 @@ EOC;
|
||||
$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->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);
|
||||
@@ -347,6 +361,7 @@ EOC;
|
||||
$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->assertSame('F', (string) $stmts[0]->stmts[6]->namespacedName);
|
||||
}
|
||||
|
||||
public function testAddRuntimeResolvedNamespacedName() {
|
||||
|
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,23 @@ $stmts[0]->returnType->types[] = new Node\Name('C');
|
||||
-----
|
||||
<?php
|
||||
function test(): A
|
||||
|B|C {}
|
||||
|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;
|
||||
}
|
@@ -0,0 +1,9 @@
|
||||
Rewrite string with variable interpolation
|
||||
-----
|
||||
<?php
|
||||
"{$e}";
|
||||
-----
|
||||
$stmts[0]->expr->parts[0]->setAttribute('origNode', null);
|
||||
-----
|
||||
<?php
|
||||
"{$e}";
|
@@ -1008,7 +1008,7 @@ function(Foo);
|
||||
Syntax error, unexpected ')', expecting T_VARIABLE from 3:18 to 3:18
|
||||
Syntax error, unexpected ')', expecting T_VARIABLE from 7:31 to 7:31
|
||||
Syntax error, unexpected ')', expecting T_VARIABLE from 11:17 to 11:17
|
||||
Syntax error, unexpected ')', expecting T_VARIABLE from 15:15 to 15:15
|
||||
Syntax error, unexpected T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG, expecting T_VARIABLE from 15:14 to 15:14
|
||||
Syntax error, unexpected ')', expecting T_VARIABLE from 19:17 to 19:17
|
||||
Syntax error, unexpected ')', expecting T_VARIABLE from 22:21 to 22:21
|
||||
Syntax error, unexpected ')', expecting T_VARIABLE from 25:13 to 25:13
|
||||
@@ -1137,7 +1137,7 @@ array(
|
||||
)
|
||||
flags: 0
|
||||
type: null
|
||||
byRef: true
|
||||
byRef: false
|
||||
variadic: false
|
||||
var: Expr_Error(
|
||||
)
|
||||
@@ -1401,4 +1401,124 @@ array(
|
||||
$a = ["a "thing"];
|
||||
-----
|
||||
!!php7
|
||||
Syntax error, unexpected T_STRING, expecting ',' or ']' or ')' from 2:11 to 2:15
|
||||
Syntax error, unexpected T_STRING, expecting ',' or ']' or ')' from 2:11 to 2:15
|
||||
-----
|
||||
<?php
|
||||
class A {
|
||||
/** @var ?string */
|
||||
private $foo
|
||||
|
||||
public function __construct(string $s) {
|
||||
$this->foo = $s;
|
||||
}
|
||||
}
|
||||
class B {
|
||||
const X = 1
|
||||
}
|
||||
-----
|
||||
!!php7
|
||||
Syntax error, unexpected T_PUBLIC, expecting ';' from 6:5 to 6:10
|
||||
Syntax error, unexpected '}', expecting ';' from 12:1 to 12:1
|
||||
array(
|
||||
0: Stmt_Class(
|
||||
attrGroups: array(
|
||||
)
|
||||
flags: 0
|
||||
name: Identifier(
|
||||
name: A
|
||||
)
|
||||
extends: null
|
||||
implements: array(
|
||||
)
|
||||
stmts: array(
|
||||
0: Stmt_Property(
|
||||
attrGroups: array(
|
||||
)
|
||||
flags: MODIFIER_PRIVATE (4)
|
||||
type: null
|
||||
props: array(
|
||||
0: Stmt_PropertyProperty(
|
||||
name: VarLikeIdentifier(
|
||||
name: foo
|
||||
)
|
||||
default: null
|
||||
)
|
||||
)
|
||||
comments: array(
|
||||
0: /** @var ?string */
|
||||
)
|
||||
)
|
||||
1: Stmt_ClassMethod(
|
||||
attrGroups: array(
|
||||
)
|
||||
flags: MODIFIER_PUBLIC (1)
|
||||
byRef: false
|
||||
name: Identifier(
|
||||
name: __construct
|
||||
)
|
||||
params: array(
|
||||
0: Param(
|
||||
attrGroups: array(
|
||||
)
|
||||
flags: 0
|
||||
type: Identifier(
|
||||
name: string
|
||||
)
|
||||
byRef: false
|
||||
variadic: false
|
||||
var: Expr_Variable(
|
||||
name: s
|
||||
)
|
||||
default: null
|
||||
)
|
||||
)
|
||||
returnType: null
|
||||
stmts: array(
|
||||
0: Stmt_Expression(
|
||||
expr: Expr_Assign(
|
||||
var: Expr_PropertyFetch(
|
||||
var: Expr_Variable(
|
||||
name: this
|
||||
)
|
||||
name: Identifier(
|
||||
name: foo
|
||||
)
|
||||
)
|
||||
expr: Expr_Variable(
|
||||
name: s
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
1: Stmt_Class(
|
||||
attrGroups: array(
|
||||
)
|
||||
flags: 0
|
||||
name: Identifier(
|
||||
name: B
|
||||
)
|
||||
extends: null
|
||||
implements: array(
|
||||
)
|
||||
stmts: array(
|
||||
0: Stmt_ClassConst(
|
||||
attrGroups: array(
|
||||
)
|
||||
flags: 0
|
||||
consts: array(
|
||||
0: Const(
|
||||
name: Identifier(
|
||||
name: X
|
||||
)
|
||||
value: Scalar_LNumber(
|
||||
value: 1
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
@@ -7,6 +7,9 @@ static fn(&$x) => $x;
|
||||
fn&($x) => $x;
|
||||
fn($x, ...$rest) => $rest;
|
||||
fn(): int => $x;
|
||||
|
||||
fn($a, $b) => $a and $b;
|
||||
fn($a, $b) => $a && $b;
|
||||
-----
|
||||
!!php7
|
||||
array(
|
||||
@@ -172,4 +175,90 @@ array(
|
||||
)
|
||||
)
|
||||
)
|
||||
6: Stmt_Expression(
|
||||
expr: Expr_ArrowFunction(
|
||||
attrGroups: array(
|
||||
)
|
||||
static: false
|
||||
byRef: false
|
||||
params: array(
|
||||
0: Param(
|
||||
attrGroups: array(
|
||||
)
|
||||
flags: 0
|
||||
type: null
|
||||
byRef: false
|
||||
variadic: false
|
||||
var: Expr_Variable(
|
||||
name: a
|
||||
)
|
||||
default: null
|
||||
)
|
||||
1: Param(
|
||||
attrGroups: array(
|
||||
)
|
||||
flags: 0
|
||||
type: null
|
||||
byRef: false
|
||||
variadic: false
|
||||
var: Expr_Variable(
|
||||
name: b
|
||||
)
|
||||
default: null
|
||||
)
|
||||
)
|
||||
returnType: null
|
||||
expr: Expr_BinaryOp_LogicalAnd(
|
||||
left: Expr_Variable(
|
||||
name: a
|
||||
)
|
||||
right: Expr_Variable(
|
||||
name: b
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
7: Stmt_Expression(
|
||||
expr: Expr_ArrowFunction(
|
||||
attrGroups: array(
|
||||
)
|
||||
static: false
|
||||
byRef: false
|
||||
params: array(
|
||||
0: Param(
|
||||
attrGroups: array(
|
||||
)
|
||||
flags: 0
|
||||
type: null
|
||||
byRef: false
|
||||
variadic: false
|
||||
var: Expr_Variable(
|
||||
name: a
|
||||
)
|
||||
default: null
|
||||
)
|
||||
1: Param(
|
||||
attrGroups: array(
|
||||
)
|
||||
flags: 0
|
||||
type: null
|
||||
byRef: false
|
||||
variadic: false
|
||||
var: Expr_Variable(
|
||||
name: b
|
||||
)
|
||||
default: null
|
||||
)
|
||||
)
|
||||
returnType: null
|
||||
expr: Expr_BinaryOp_BooleanAnd(
|
||||
left: Expr_Variable(
|
||||
name: a
|
||||
)
|
||||
right: Expr_Variable(
|
||||
name: b
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
17
test/code/parser/expr/varVarPos.test
Normal file
17
test/code/parser/expr/varVarPos.test
Normal file
@@ -0,0 +1,17 @@
|
||||
Variable variable positions
|
||||
-----
|
||||
<?php
|
||||
$$$x;
|
||||
-----
|
||||
!!positions
|
||||
array(
|
||||
0: Stmt_Expression[2:1 - 2:5](
|
||||
expr: Expr_Variable[2:1 - 2:4](
|
||||
name: Expr_Variable[2:2 - 2:4](
|
||||
name: Expr_Variable[2:3 - 2:4](
|
||||
name: x
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
@@ -2,26 +2,28 @@ Class position
|
||||
-----
|
||||
<?php
|
||||
|
||||
class A {
|
||||
}
|
||||
if (1);
|
||||
|
||||
try {
|
||||
} catch (Exception $e) {
|
||||
}
|
||||
|
||||
class B {
|
||||
}
|
||||
|
||||
?>
|
||||
class C {}
|
||||
-----
|
||||
!!positions
|
||||
array(
|
||||
0: Stmt_Class[3:1 - 4:1](
|
||||
0: Stmt_If[3:1 - 3:7](
|
||||
cond: Scalar_LNumber[3:5 - 3:5](
|
||||
value: 1
|
||||
)
|
||||
stmts: array(
|
||||
)
|
||||
elseifs: array(
|
||||
)
|
||||
else: null
|
||||
)
|
||||
1: Stmt_Class[5:1 - 5:10](
|
||||
attrGroups: array(
|
||||
)
|
||||
flags: 0
|
||||
name: Identifier[3:7 - 3:7](
|
||||
name: A
|
||||
name: Identifier[5:7 - 5:7](
|
||||
name: C
|
||||
)
|
||||
extends: null
|
||||
implements: array(
|
||||
@@ -29,36 +31,62 @@ array(
|
||||
stmts: array(
|
||||
)
|
||||
)
|
||||
1: Stmt_TryCatch[6:1 - 8:1](
|
||||
)
|
||||
-----
|
||||
<?php
|
||||
|
||||
if (1);
|
||||
|
||||
trait X {}
|
||||
-----
|
||||
!!positions
|
||||
array(
|
||||
0: Stmt_If[3:1 - 3:7](
|
||||
cond: Scalar_LNumber[3:5 - 3:5](
|
||||
value: 1
|
||||
)
|
||||
stmts: array(
|
||||
)
|
||||
catches: array(
|
||||
0: Stmt_Catch[7:3 - 8:1](
|
||||
types: array(
|
||||
0: Name[7:10 - 7:18](
|
||||
parts: array(
|
||||
0: Exception
|
||||
)
|
||||
)
|
||||
)
|
||||
var: Expr_Variable[7:20 - 7:21](
|
||||
name: e
|
||||
)
|
||||
stmts: array(
|
||||
)
|
||||
)
|
||||
elseifs: array(
|
||||
)
|
||||
finally: null
|
||||
else: null
|
||||
)
|
||||
2: Stmt_Class[10:1 - 11:1](
|
||||
1: Stmt_Trait[5:1 - 5:10](
|
||||
attrGroups: array(
|
||||
)
|
||||
flags: 0
|
||||
name: Identifier[10:7 - 10:7](
|
||||
name: B
|
||||
name: Identifier[5:7 - 5:7](
|
||||
name: X
|
||||
)
|
||||
extends: null
|
||||
implements: array(
|
||||
stmts: array(
|
||||
)
|
||||
)
|
||||
)
|
||||
-----
|
||||
<?php
|
||||
|
||||
if (1);
|
||||
|
||||
interface X {}
|
||||
-----
|
||||
!!positions
|
||||
array(
|
||||
0: Stmt_If[3:1 - 3:7](
|
||||
cond: Scalar_LNumber[3:5 - 3:5](
|
||||
value: 1
|
||||
)
|
||||
stmts: array(
|
||||
)
|
||||
elseifs: array(
|
||||
)
|
||||
else: null
|
||||
)
|
||||
1: Stmt_Interface[5:1 - 5:14](
|
||||
attrGroups: array(
|
||||
)
|
||||
name: Identifier[5:11 - 5:11](
|
||||
name: X
|
||||
)
|
||||
extends: array(
|
||||
)
|
||||
stmts: array(
|
||||
)
|
||||
|
@@ -78,11 +78,11 @@ array(
|
||||
-----
|
||||
<?php
|
||||
class A {
|
||||
final const X = 1;
|
||||
readonly const X = 1;
|
||||
}
|
||||
-----
|
||||
!!php7
|
||||
Cannot use 'final' as constant modifier from 3:5 to 3:9
|
||||
Cannot use 'readonly' as constant modifier from 3:5 to 3:12
|
||||
array(
|
||||
0: Stmt_Class(
|
||||
attrGroups: array(
|
||||
@@ -98,7 +98,7 @@ array(
|
||||
0: Stmt_ClassConst(
|
||||
attrGroups: array(
|
||||
)
|
||||
flags: MODIFIER_FINAL (32)
|
||||
flags: MODIFIER_READONLY (64)
|
||||
consts: array(
|
||||
0: Const(
|
||||
name: Identifier(
|
||||
@@ -150,4 +150,4 @@ array(
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
@@ -7,6 +7,7 @@ class Foo {
|
||||
public const B = 2;
|
||||
protected const C = 3;
|
||||
private const D = 4;
|
||||
final const E = 5;
|
||||
}
|
||||
-----
|
||||
!!php7
|
||||
@@ -82,6 +83,21 @@ array(
|
||||
)
|
||||
)
|
||||
)
|
||||
4: Stmt_ClassConst(
|
||||
attrGroups: array(
|
||||
)
|
||||
flags: MODIFIER_FINAL (32)
|
||||
consts: array(
|
||||
0: Const(
|
||||
name: Identifier(
|
||||
name: E
|
||||
)
|
||||
value: Scalar_LNumber(
|
||||
value: 5
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
65
test/code/parser/stmt/class/enum.test
Normal file
65
test/code/parser/stmt/class/enum.test
Normal file
@@ -0,0 +1,65 @@
|
||||
Enum
|
||||
-----
|
||||
<?php
|
||||
|
||||
enum A {}
|
||||
enum B implements Bar, Baz {
|
||||
}
|
||||
enum C: int implements Bar {}
|
||||
-----
|
||||
!!php7
|
||||
array(
|
||||
0: Stmt_Enum(
|
||||
attrGroups: array(
|
||||
)
|
||||
name: Identifier(
|
||||
name: A
|
||||
)
|
||||
scalarType: null
|
||||
implements: array(
|
||||
)
|
||||
stmts: array(
|
||||
)
|
||||
)
|
||||
1: Stmt_Enum(
|
||||
attrGroups: array(
|
||||
)
|
||||
name: Identifier(
|
||||
name: B
|
||||
)
|
||||
scalarType: null
|
||||
implements: array(
|
||||
0: Name(
|
||||
parts: array(
|
||||
0: Bar
|
||||
)
|
||||
)
|
||||
1: Name(
|
||||
parts: array(
|
||||
0: Baz
|
||||
)
|
||||
)
|
||||
)
|
||||
stmts: array(
|
||||
)
|
||||
)
|
||||
2: Stmt_Enum(
|
||||
attrGroups: array(
|
||||
)
|
||||
name: Identifier(
|
||||
name: C
|
||||
)
|
||||
scalarType: Identifier(
|
||||
name: int
|
||||
)
|
||||
implements: array(
|
||||
0: Name(
|
||||
parts: array(
|
||||
0: Bar
|
||||
)
|
||||
)
|
||||
)
|
||||
stmts: array(
|
||||
)
|
||||
)
|
||||
)
|
67
test/code/parser/stmt/class/enum_with_string.test
Normal file
67
test/code/parser/stmt/class/enum_with_string.test
Normal file
@@ -0,0 +1,67 @@
|
||||
Enum
|
||||
-----
|
||||
<?php
|
||||
|
||||
enum Suit: string
|
||||
{
|
||||
case Hearts = 'H';
|
||||
case Diamonds;
|
||||
case Clubs = 'C';
|
||||
case Spades = 'S';
|
||||
}
|
||||
-----
|
||||
!!php7
|
||||
array(
|
||||
0: Stmt_Enum(
|
||||
attrGroups: array(
|
||||
)
|
||||
name: Identifier(
|
||||
name: Suit
|
||||
)
|
||||
scalarType: Identifier(
|
||||
name: string
|
||||
)
|
||||
implements: array(
|
||||
)
|
||||
stmts: array(
|
||||
0: Stmt_EnumCase(
|
||||
attrGroups: array(
|
||||
)
|
||||
name: Identifier(
|
||||
name: Hearts
|
||||
)
|
||||
expr: Scalar_String(
|
||||
value: H
|
||||
)
|
||||
)
|
||||
1: Stmt_EnumCase(
|
||||
attrGroups: array(
|
||||
)
|
||||
name: Identifier(
|
||||
name: Diamonds
|
||||
)
|
||||
expr: null
|
||||
)
|
||||
2: Stmt_EnumCase(
|
||||
attrGroups: array(
|
||||
)
|
||||
name: Identifier(
|
||||
name: Clubs
|
||||
)
|
||||
expr: Scalar_String(
|
||||
value: C
|
||||
)
|
||||
)
|
||||
3: Stmt_EnumCase(
|
||||
attrGroups: array(
|
||||
)
|
||||
name: Identifier(
|
||||
name: Spades
|
||||
)
|
||||
expr: Scalar_String(
|
||||
value: S
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
@@ -66,6 +66,40 @@ array(
|
||||
)
|
||||
)
|
||||
-----
|
||||
<?php class A { readonly readonly $a; }
|
||||
-----
|
||||
!!php7
|
||||
Multiple readonly modifiers are not allowed from 1:26 to 1:33
|
||||
array(
|
||||
0: Stmt_Class(
|
||||
attrGroups: array(
|
||||
)
|
||||
flags: 0
|
||||
name: Identifier(
|
||||
name: A
|
||||
)
|
||||
extends: null
|
||||
implements: array(
|
||||
)
|
||||
stmts: array(
|
||||
0: Stmt_Property(
|
||||
attrGroups: array(
|
||||
)
|
||||
flags: MODIFIER_READONLY (64)
|
||||
type: null
|
||||
props: array(
|
||||
0: Stmt_PropertyProperty(
|
||||
name: VarLikeIdentifier(
|
||||
name: a
|
||||
)
|
||||
default: null
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
-----
|
||||
<?php class A { abstract abstract function a(); }
|
||||
-----
|
||||
Multiple abstract modifiers are not allowed from 1:26 to 1:33
|
||||
@@ -221,6 +255,31 @@ array(
|
||||
)
|
||||
)
|
||||
-----
|
||||
<?php readonly class A { }
|
||||
// Type in the partial parse could conceivably be any of 0, 16 or 32
|
||||
-----
|
||||
Syntax error, unexpected T_READONLY from 1:7 to 1:14
|
||||
array(
|
||||
0: Stmt_Class(
|
||||
attrGroups: array(
|
||||
)
|
||||
flags: 0
|
||||
name: Identifier(
|
||||
name: A
|
||||
)
|
||||
extends: null
|
||||
implements: array(
|
||||
)
|
||||
stmts: array(
|
||||
)
|
||||
)
|
||||
1: Stmt_Nop(
|
||||
comments: array(
|
||||
0: // Type in the partial parse could conceivably be any of 0, 16 or 32
|
||||
)
|
||||
)
|
||||
)
|
||||
-----
|
||||
<?php class A { abstract $a; }
|
||||
-----
|
||||
Properties cannot be declared abstract from 1:17 to 1:24
|
||||
|
@@ -6,6 +6,7 @@ class A {
|
||||
public string $a;
|
||||
protected static D $b;
|
||||
private ?float $c;
|
||||
readonly static public ?int $d;
|
||||
}
|
||||
-----
|
||||
!!php7
|
||||
@@ -73,6 +74,24 @@ array(
|
||||
)
|
||||
)
|
||||
)
|
||||
3: Stmt_Property(
|
||||
attrGroups: array(
|
||||
)
|
||||
flags: MODIFIER_PUBLIC | MODIFIER_STATIC | MODIFIER_READONLY (73)
|
||||
type: NullableType(
|
||||
type: Identifier(
|
||||
name: int
|
||||
)
|
||||
)
|
||||
props: array(
|
||||
0: Stmt_PropertyProperty(
|
||||
name: VarLikeIdentifier(
|
||||
name: d
|
||||
)
|
||||
default: null
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
35
test/code/parser/stmt/class/readonlyMethod.test
Normal file
35
test/code/parser/stmt/class/readonlyMethod.test
Normal file
@@ -0,0 +1,35 @@
|
||||
Methods cannot be readonly
|
||||
-----
|
||||
<?php class A { readonly function foo() {} }
|
||||
-----
|
||||
!!php7
|
||||
Method foo() cannot be readonly from 1:17 to 1:24
|
||||
array(
|
||||
0: Stmt_Class(
|
||||
attrGroups: array(
|
||||
)
|
||||
flags: 0
|
||||
name: Identifier(
|
||||
name: A
|
||||
)
|
||||
extends: null
|
||||
implements: array(
|
||||
)
|
||||
stmts: array(
|
||||
0: Stmt_ClassMethod(
|
||||
attrGroups: array(
|
||||
)
|
||||
flags: MODIFIER_READONLY (64)
|
||||
byRef: false
|
||||
name: Identifier(
|
||||
name: foo
|
||||
)
|
||||
params: array(
|
||||
)
|
||||
returnType: null
|
||||
stmts: array(
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
23
test/code/parser/stmt/function/neverType.test
Normal file
23
test/code/parser/stmt/function/neverType.test
Normal file
@@ -0,0 +1,23 @@
|
||||
Never type
|
||||
-----
|
||||
<?php
|
||||
function test(): never {}
|
||||
-----
|
||||
!!php7
|
||||
array(
|
||||
0: Stmt_Function(
|
||||
attrGroups: array(
|
||||
)
|
||||
byRef: false
|
||||
name: Identifier(
|
||||
name: test
|
||||
)
|
||||
params: array(
|
||||
)
|
||||
returnType: Identifier(
|
||||
name: never
|
||||
)
|
||||
stmts: array(
|
||||
)
|
||||
)
|
||||
)
|
@@ -8,6 +8,8 @@ fn(&$x) => $x;
|
||||
fn&($x) => $x;
|
||||
static fn($x, ...$rest) => $rest;
|
||||
fn(): int => $x;
|
||||
fn($a, $b) => $a and $b;
|
||||
fn($a, $b) => $a && $b;
|
||||
-----
|
||||
!!php7
|
||||
fn($a) => $a;
|
||||
@@ -16,3 +18,5 @@ fn(&$x) => $x;
|
||||
fn&($x) => $x;
|
||||
static fn($x, ...$rest) => $rest;
|
||||
fn(): int => $x;
|
||||
fn($a, $b) => $a and $b;
|
||||
fn($a, $b) => $a && $b;
|
@@ -59,7 +59,7 @@ $a = $b = $c = $d = $f && true;
|
||||
($a = $b = $c = $d = $f) && true;
|
||||
$a = $b = $c = $d = $f and true;
|
||||
$a = $b = $c = $d = ($f and true);
|
||||
$a ? $b : $c ? $d : $e ? $f : $g;
|
||||
(($a ? $b : $c) ? $d : $e) ? $f : $g;
|
||||
$a ? $b : ($c ? $d : ($e ? $f : $g));
|
||||
$a ? $b ? $c : $d : $f;
|
||||
$a ?? $b ?? $c;
|
||||
|
@@ -4,20 +4,30 @@ Escape sequences in double-quoted strings
|
||||
"\n\r\t\f\v\$\"\\";
|
||||
"@@{ implode(range("\0", "\37")) }@@";
|
||||
"\0000\0001";
|
||||
"äöü";
|
||||
"\xc0\x80";
|
||||
"\xd0\x01";
|
||||
"\xf0\x80\x80";
|
||||
|
||||
<<<DOC
|
||||
\n\r\t\f\v\$\"\\
|
||||
@@{ implode(range("\0", "\37")) }@@
|
||||
\0000\0001
|
||||
äöü
|
||||
DOC;
|
||||
|
||||
-----
|
||||
"\n\r\t\f\v\$\"\\";
|
||||
"\0\1\2\3\4\5\6\7\10\t\n\v\f\r\16\17\20\21\22\23\24\25\26\27\30\31\32\33\34\35\36\37";
|
||||
"\0000\0001";
|
||||
"\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\v\f\r\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f";
|
||||
"\x000\x001";
|
||||
"äöü";
|
||||
"\xc0\x80";
|
||||
"\xd0\x01";
|
||||
"\xf0\x80\x80";
|
||||
<<<DOC
|
||||
@@{ "\n\r" }@@\t\f\v\$\\"\\
|
||||
\0\1\2\3\4\5\6\7\10\t@@{ "\n" }@@\v\f@@{ "\r" }@@\16\17\20\21\22\23\24\25\26\27\30\31\32\33\34\35\36\37
|
||||
\0000\0001
|
||||
\x00\x01\x02\x03\x04\x05\x06\x07\x08\t@@{ "\n" }@@\v\f@@{ "\r" }@@\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f
|
||||
\x000\x001
|
||||
äöü
|
||||
DOC
|
||||
;
|
@@ -55,4 +55,13 @@ HTML<?php echo 'PHP'; ?>HTML
|
||||
-----
|
||||
HTML<?php
|
||||
echo 'PHP';
|
||||
?>HTML
|
||||
?>HTML
|
||||
-----
|
||||
<?php
|
||||
echo 'PHP';
|
||||
?>@@{ "\r\r\n" }@@Test
|
||||
-----
|
||||
<?php
|
||||
|
||||
echo 'PHP';
|
||||
?>@@{ "\n\n" }@@Test
|
39
test/code/prettyPrinter/stmt/enum.test
Normal file
39
test/code/prettyPrinter/stmt/enum.test
Normal file
@@ -0,0 +1,39 @@
|
||||
Enum
|
||||
-----
|
||||
<?php
|
||||
|
||||
enum A implements B
|
||||
{
|
||||
case X;
|
||||
case Y;
|
||||
|
||||
public function foo() {}
|
||||
}
|
||||
|
||||
enum B: int {
|
||||
case X = 1;
|
||||
case Y = 2;
|
||||
}
|
||||
|
||||
enum C: string implements D {
|
||||
case Z = 'A';
|
||||
}
|
||||
-----
|
||||
!!php7
|
||||
enum A implements B
|
||||
{
|
||||
case X;
|
||||
case Y;
|
||||
public function foo()
|
||||
{
|
||||
}
|
||||
}
|
||||
enum B : int
|
||||
{
|
||||
case X = 1;
|
||||
case Y = 2;
|
||||
}
|
||||
enum C : string implements D
|
||||
{
|
||||
case Z = 'A';
|
||||
}
|
@@ -8,6 +8,7 @@ class A
|
||||
public string $b;
|
||||
protected static ?float $c = 5.0;
|
||||
private static ?self $d;
|
||||
public readonly int|float $e;
|
||||
}
|
||||
-----
|
||||
!!php7
|
||||
@@ -17,4 +18,5 @@ class A
|
||||
public string $b;
|
||||
protected static ?float $c = 5.0;
|
||||
private static ?self $d;
|
||||
public readonly int|float $e;
|
||||
}
|
||||
|
@@ -2,4 +2,4 @@ VERSION=$1
|
||||
wget -q https://github.com/php/php-src/archive/php-$VERSION.tar.gz
|
||||
mkdir -p ./data/php-src
|
||||
tar -xzf ./php-$VERSION.tar.gz -C ./data/php-src --strip-components=1
|
||||
php -n test_old/run.php --verbose --no-progress --php-version=$VERSION PHP ./data/php-src
|
||||
php test_old/run.php --verbose --no-progress --php-version=$VERSION PHP ./data/php-src
|
||||
|
Reference in New Issue
Block a user