mirror of
https://github.com/nikic/PHP-Parser.git
synced 2025-06-25 01:25:51 +02:00
Compare commits
90 Commits
v4.17.0
...
v5.0.0alph
Author | SHA1 | Date | |
---|---|---|---|
96037b3d33 | |||
8d02d37e42 | |||
e68b17cc3b | |||
f63081a57c | |||
44c6a97705 | |||
9a230cd333 | |||
a772853307 | |||
c585a2d766 | |||
8ed76726aa | |||
5f3ad31501 | |||
4917c71a91 | |||
e1345f0c09 | |||
03ccfa3dd4 | |||
a3b0541c71 | |||
c42290ae42 | |||
a44faa6328 | |||
11caa3b9cc | |||
f4ec6a1e53 | |||
23835d20ef | |||
66b20bd6bc | |||
2b562b72a8 | |||
f5b56a5c4c | |||
a2608f0b74 | |||
9dca6f1d37 | |||
035c1c7cd2 | |||
8be56afd2d | |||
0933986293 | |||
bf39f6a4e0 | |||
892b07c428 | |||
9857581ee8 | |||
09c6048df1 | |||
3c3bcd3125 | |||
a5033e3860 | |||
a2753c8218 | |||
dd63ddbc24 | |||
f62b2bfdec | |||
68fc1ba4cb | |||
53b907d405 | |||
f828b76972 | |||
0201a7ee3f | |||
9b2a01aa0c | |||
652fb0c6c1 | |||
ea9d6b2238 | |||
34d8681488 | |||
1f504d2c7d | |||
c55c7a2ac3 | |||
9ef528f3f5 | |||
e61bb11989 | |||
cf0cd6003e | |||
646b490735 | |||
c218db3e16 | |||
84813dc1e4 | |||
59145a4443 | |||
7bf6348240 | |||
a73c8ee03b | |||
1e89658cae | |||
050342b5df | |||
a3f2bb634d | |||
de4ac93023 | |||
4021a63cef | |||
653757bec6 | |||
572af7fff2 | |||
0086a261d0 | |||
5aae65e627 | |||
0ea134a507 | |||
a38a60b7dd | |||
c878d7195d | |||
6e0eec807e | |||
71ed641cd4 | |||
e3ff8cf035 | |||
b0469d127e | |||
23be1f9bd1 | |||
3fd30f64bc | |||
55f29b152c | |||
d3d1297c0d | |||
b4902cefe4 | |||
b6d11da541 | |||
bdd131d3ec | |||
472e163ffa | |||
fe9db376a1 | |||
aff98bbf16 | |||
7c445bb608 | |||
5af93eee52 | |||
27fe7a68c0 | |||
6c0b63d9af | |||
87387caf8f | |||
9c5eb3ccba | |||
e70541f136 | |||
5466ee365f | |||
1f416d9052 |
1
.gitattributes
vendored
1
.gitattributes
vendored
@ -6,5 +6,6 @@
|
||||
.gitattributes export-ignore
|
||||
.gitignore export-ignore
|
||||
CHANGELOG.md export-ignore
|
||||
CONTRIBUTING.md export-ignore
|
||||
phpunit.xml.dist export-ignore
|
||||
UPGRADE-*.md export-ignore
|
||||
|
20
.github/workflows/main.yml
vendored
20
.github/workflows/main.yml
vendored
@ -5,17 +5,17 @@ on:
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
tests_70:
|
||||
tests_coverage:
|
||||
runs-on: "ubuntu-latest"
|
||||
name: "PHP 7.0 Unit Tests"
|
||||
name: "PHP 7.1 Unit Tests (with coverage)"
|
||||
steps:
|
||||
- name: "Checkout"
|
||||
uses: "actions/checkout@v3"
|
||||
uses: "actions/checkout@v2"
|
||||
- name: "Install PHP"
|
||||
uses: "shivammathur/setup-php@v2"
|
||||
with:
|
||||
coverage: "xdebug"
|
||||
php-version: "7.0"
|
||||
php-version: "7.1"
|
||||
tools: composer:v2
|
||||
- name: "Install dependencies"
|
||||
run: |
|
||||
@ -34,16 +34,14 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
php-version:
|
||||
- "7.1"
|
||||
- "7.2"
|
||||
- "7.3"
|
||||
- "7.4"
|
||||
- "8.0"
|
||||
- "8.1"
|
||||
- "8.2"
|
||||
steps:
|
||||
- name: "Checkout"
|
||||
uses: "actions/checkout@v3"
|
||||
uses: "actions/checkout@v2"
|
||||
- name: "Install PHP"
|
||||
uses: "shivammathur/setup-php@v2"
|
||||
with:
|
||||
@ -59,7 +57,7 @@ jobs:
|
||||
name: "PHP 7.3 Code on PHP 8.0 Integration Tests"
|
||||
steps:
|
||||
- name: "Checkout"
|
||||
uses: "actions/checkout@v3"
|
||||
uses: "actions/checkout@v2"
|
||||
- name: "Install PHP"
|
||||
uses: "shivammathur/setup-php@v2"
|
||||
with:
|
||||
@ -72,15 +70,15 @@ jobs:
|
||||
run: "test_old/run-php-src.sh 7.3.21"
|
||||
test_old_80_70:
|
||||
runs-on: "ubuntu-latest"
|
||||
name: "PHP 8.1 Code on PHP 7.0 Integration Tests"
|
||||
name: "PHP 8.1 Code on PHP 7.1 Integration Tests"
|
||||
steps:
|
||||
- name: "Checkout"
|
||||
uses: "actions/checkout@v3"
|
||||
uses: "actions/checkout@v2"
|
||||
- name: "Install PHP"
|
||||
uses: "shivammathur/setup-php@v2"
|
||||
with:
|
||||
coverage: "none"
|
||||
php-version: "7.0"
|
||||
php-version: "7.1"
|
||||
tools: composer:v2
|
||||
- name: "Install PHP 8 dependencies"
|
||||
run: "composer update --no-progress --prefer-dist"
|
||||
|
26
.php-cs-fixer.dist.php
Normal file
26
.php-cs-fixer.dist.php
Normal file
@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
$finder = PhpCsFixer\Finder::create()
|
||||
->exclude('PhpParser/Parser')
|
||||
->in(__DIR__ . '/lib')
|
||||
->in(__DIR__ . '/test')
|
||||
->in(__DIR__ . '/grammar')
|
||||
;
|
||||
|
||||
$config = new PhpCsFixer\Config();
|
||||
return $config->setRiskyAllowed(true)
|
||||
->setRules([
|
||||
'@PSR12' => true,
|
||||
// We use PSR12 with consistent brace placement.
|
||||
'curly_braces_position' => [
|
||||
'functions_opening_brace' => 'same_line',
|
||||
'classes_opening_brace' => 'same_line',
|
||||
],
|
||||
// declare(strict_types=1) on the same line as <?php.
|
||||
'blank_line_after_opening_tag' => false,
|
||||
'declare_strict_types' => true,
|
||||
// Keep argument formatting for now.
|
||||
'method_argument_space' => ['on_multiline' => 'ignore'],
|
||||
])
|
||||
->setFinder($finder)
|
||||
;
|
80
CHANGELOG.md
80
CHANGELOG.md
@ -1,60 +1,36 @@
|
||||
Version 4.17.0 (2023-08-13)
|
||||
---------------------------
|
||||
Version 5.0.0-dev
|
||||
-----------------
|
||||
|
||||
Nothing yet.
|
||||
|
||||
Version 5.0.0-alpha1 (2022-09-04)
|
||||
---------------------------------
|
||||
|
||||
See UPGRADE-5.0 for detailed migration instructions.
|
||||
|
||||
### Changed
|
||||
|
||||
* PHP 7.1 is now required to run PHP-Parser.
|
||||
* Formatting of the standard pretty printer has been adjusted to match PSR-12 more closely.
|
||||
* The internal token representation now uses a `PhpParser\Token` class, which is compatible with
|
||||
PHP 8 token representation (`PhpToken`).
|
||||
* Destructuring is now always represented using `Expr\List_` nodes, even if it uses `[]` syntax.
|
||||
* Renamed a number of node classes, and moved things that were not real expressions/statements
|
||||
outside the `Expr`/`Stmt` hierarchy. Compatibility shims for the old names have been retained.
|
||||
|
||||
### Added
|
||||
|
||||
* [PHP 8.3] Added support for typed class constants.
|
||||
* [PHP 8.3] Added supprot for dynamic class constant fetch.
|
||||
* [PHP 8.3] Added support for readonly anonymous classes.
|
||||
* Added `PhpVersion` class, which is accepted in a number of places (e.g. ParserFactory, Parser,
|
||||
Lexer, PrettyPrinter) and gives more precise control over the PHP version being targeted.
|
||||
* Added PHP 8 parser though it only differs from the PHP 7 parser in concatenation precedence.
|
||||
* Added `Parser::getLexer()` method.
|
||||
* Added a `Modifiers` class, as a replacement for `Stmt\Class_::MODIFIER_*`.
|
||||
* Added support for returning an array or `REMOVE_NODE` from `NodeVisitor::enterNode()`.
|
||||
|
||||
### Fixed
|
||||
### Removed
|
||||
|
||||
* Fixed missing required parentheses when pretty printing new with an expression class name.
|
||||
* Fixed missing required parentheses when pretty printing `(CONST)::$x` and similar.
|
||||
|
||||
Version 4.16.0 (2023-06-25)
|
||||
---------------------------
|
||||
|
||||
### Added
|
||||
|
||||
* Added `Name::getParts()` method for forward-compatibility with PHP-Parser 5.
|
||||
|
||||
### Deprecated
|
||||
|
||||
* Deprecated direct access to `Name::$parts`, which will be removed in PHP-Parser 5.
|
||||
|
||||
Version 4.15.5 (2023-05-19)
|
||||
---------------------------
|
||||
|
||||
### Added
|
||||
|
||||
* Added `makePrivate()`, `makeProtected()`, `makePublic()` and `makeReadonly()` methods to
|
||||
`Builder\Param` to allow the creation of promoted parameters.
|
||||
|
||||
Version 4.15.4 (2023-03-05)
|
||||
---------------------------
|
||||
|
||||
### Fixed
|
||||
|
||||
* Fixed formatting-preservation for alternative if syntax with trailing comments.
|
||||
|
||||
Version 4.15.3 (2023-01-16)
|
||||
---------------------------
|
||||
|
||||
### Fixed
|
||||
|
||||
* Support readonly property with PHP 8.2 DNF type.
|
||||
* Fixed PHP attribute group and PHP-Parser attribute mixup in EnumCase builder.
|
||||
|
||||
Version 4.15.2 (2022-11-12)
|
||||
---------------------------
|
||||
|
||||
### Fixed
|
||||
|
||||
* Fixed parsing of large hex float literals that contain an "e" character.
|
||||
* Fixed tests to pass on 32-bit.
|
||||
* Fixed generation of invalid code when using formatting-preserving pretty printer with code that
|
||||
uses inline HTML.
|
||||
* The PHP 5 parser has been removed. The PHP 7 parser has been adjusted to deal with PHP 5 code
|
||||
more gracefully.
|
||||
|
||||
Version 4.15.1 (2022-09-04)
|
||||
---------------------------
|
||||
|
4
CONTRIBUTING.md
Normal file
4
CONTRIBUTING.md
Normal file
@ -0,0 +1,4 @@
|
||||
## Coding Style
|
||||
|
||||
This project uses PSR-12 with consistent brace placement. This means that the opening brace is
|
||||
always on the same line, even for class and method declarations.
|
11
README.md
11
README.md
@ -3,9 +3,11 @@ PHP Parser
|
||||
|
||||
[](https://coveralls.io/github/nikic/PHP-Parser?branch=master)
|
||||
|
||||
This is a PHP 5.2 to PHP 8.2 parser written in PHP. Its purpose is to simplify static code analysis and
|
||||
This is a PHP parser written in PHP. Its purpose is to simplify static code analysis and
|
||||
manipulation.
|
||||
|
||||
[Documentation for version 5.x][doc_master] (in development; for running on PHP >= 7.1; for parsing PHP 7.0 to PHP 8.2, with limited support for parsing PHP 5.x).
|
||||
|
||||
[**Documentation for version 4.x**][doc_4_x] (stable; for running on PHP >= 7.0; for parsing PHP 5.2 to PHP 8.2).
|
||||
|
||||
[Documentation for version 3.x][doc_3_x] (unsupported; for running on PHP >= 5.5; for parsing PHP 5.2 to PHP 7.2).
|
||||
@ -15,12 +17,12 @@ Features
|
||||
|
||||
The main features provided by this library are:
|
||||
|
||||
* Parsing PHP 5, PHP 7, and PHP 8 code into an abstract syntax tree (AST).
|
||||
* Parsing 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.
|
||||
* Converting an AST back to PHP code.
|
||||
* Experimental: Formatting can be preserved for partially changed ASTs.
|
||||
* Formatting can be preserved for partially changed ASTs.
|
||||
* Infrastructure to traverse and modify ASTs.
|
||||
* Resolution of namespaced names.
|
||||
* Evaluation of constant expressions.
|
||||
@ -51,7 +53,7 @@ function test($foo)
|
||||
}
|
||||
CODE;
|
||||
|
||||
$parser = (new ParserFactory)->create(ParserFactory::PREFER_PHP7);
|
||||
$parser = (new ParserFactory())->createForNewestSupportedVersion();
|
||||
try {
|
||||
$ast = $parser->parse($code);
|
||||
} catch (Error $error) {
|
||||
@ -223,3 +225,4 @@ Component documentation:
|
||||
|
||||
[doc_3_x]: https://github.com/nikic/PHP-Parser/tree/3.x/doc
|
||||
[doc_4_x]: https://github.com/nikic/PHP-Parser/tree/4.x/doc
|
||||
[doc_master]: https://github.com/nikic/PHP-Parser/tree/master/doc
|
||||
|
188
UPGRADE-5.0.md
Normal file
188
UPGRADE-5.0.md
Normal file
@ -0,0 +1,188 @@
|
||||
Upgrading from PHP-Parser 4.x to 5.0
|
||||
====================================
|
||||
|
||||
### PHP version requirements
|
||||
|
||||
PHP-Parser now requires PHP 7.1 or newer to run. It is however still possible to *parse* code for older versions, while running on a newer version.
|
||||
|
||||
### PHP 5 parsing support
|
||||
|
||||
The dedicated parser for PHP 5 has been removed. The PHP 7 parser now accepts a `PhpVersion` argument, which can be used to improve compatibility with older PHP versions.
|
||||
|
||||
In particular, if an older `PhpVersion` is specified, then:
|
||||
|
||||
* For versions before PHP 7.0, `$foo =& new Bar()` assignments are allowed without error.
|
||||
* For versions before PHP 7.0, invalid octal literals `089` are allowed without error.
|
||||
* Type hints are interpreted as a class `Name` or as a built-in `Identifier` depending on PHP
|
||||
version, for example `int` is treated as a class name on PHP 5.6 and as a built-in on PHP 7.0.
|
||||
|
||||
However, some aspects of PHP 5 parsing are no longer supported:
|
||||
|
||||
* Some variables like `$$foo[0]` are valid in both PHP 5 and PHP 7, but have different interpretation. In that case, the PHP 7 AST will always be constructed (`($$foo)[0]` rather than `${$foo[0]}`).
|
||||
* Declarations of the form `global $$var[0]` are not supported in PHP 7 and will cause a parse error. In error recovery mode, it is possible to continue parsing after such declarations.
|
||||
* The PHP 7 parser will accept many constructs that are not valid in PHP 5. However, this was also true of the dedicated PHP 5 parser.
|
||||
|
||||
The following symbols are affected by this removal:
|
||||
|
||||
* The `PhpParser\Parser\Php5` class has been removed.
|
||||
* The `PhpParser\Parser\Multiple` class has been removed. While not strictly related to PHP 5 support, this functionality is no longer useful without it.
|
||||
* The `PhpParser\ParserFactory::ONLY_PHP5` and `PREFER_PHP5` options have been removed.
|
||||
* The `PhpParser\ParserFactory::PREFER_PHP7` option is now equivalent to `ONLY_PHP7`.
|
||||
|
||||
### Changes to the parser factory
|
||||
|
||||
The `ParserFactory::create()` method is deprecated in favor of three new methods that provide more fine-grained control over the PHP version being targeted:
|
||||
|
||||
* `createForNewestSupportedVersion()`: Use this if you don't know the PHP version of the code you're parsing. It's better to assume a too new version than a too old one.
|
||||
* `createForHostVersion()`: Use this if you're parsing code for the PHP version you're running on.
|
||||
* `createForVersion()`: Use this if you know the PHP version of the code you want to parse.
|
||||
|
||||
In all cases, the PHP version is a fairly weak hint that is only used on a best-effort basis. The parser will usually accept code for newer versions if it does not have any backwards-compatibility implications.
|
||||
|
||||
For example, if you specify version `"8.0"`, then `class ReadOnly {}` is treated as a valid class declaration, while using `public readonly int $prop` will lead to a parse error. However, `final public const X = Y;` will be accepted in both cases.
|
||||
|
||||
```php
|
||||
use PhpParser\ParserFactory;
|
||||
use PhpParser\PhpVersion;
|
||||
|
||||
$factory = new ParserFactory;
|
||||
|
||||
# Before
|
||||
$parser = $factory->create(ParserFactory::PREFER_PHP7);
|
||||
|
||||
# After (this is roughly equivalent to PREFER_PHP7 behavior)
|
||||
$parser = $factory->createForNewestSupportedVersion();
|
||||
# Or
|
||||
$parser = $factory->createForHostVersion();
|
||||
|
||||
# Before
|
||||
$parser = $factory->create(ParserFactory::ONLY_PHP5);
|
||||
# After (supported on a best-effort basis)
|
||||
$parser = $factory->createForVersion(PhpVersion::fromString("5.6"));
|
||||
```
|
||||
|
||||
### Changes to the array destructuring representation
|
||||
|
||||
Previously, the `list($x) = $y` destructuring syntax was represented using a `Node\Expr\List_`
|
||||
node, while `[$x] = $y` used a `Node\Expr\Array_` node, the same used for the creation (rather than
|
||||
destructuring) of arrays.
|
||||
|
||||
Now, destructuring is always represented using `Node\Expr\List_`. The `kind` attribute with value
|
||||
`Node\Expr\List_::KIND_LIST` or `Node\Expr\List_::KIND_ARRAY` specifies which syntax was actually
|
||||
used.
|
||||
|
||||
### Renamed nodes
|
||||
|
||||
A number of AST nodes have been renamed or moved in the AST hierarchy:
|
||||
|
||||
* `Node\Scalar\LNumber` is now `Node\Scalar\Int_`.
|
||||
* `Node\Scalar\DNumber` is now `Node\Scalar\Float_`.
|
||||
* `Node\Scalar\Encapsed` is now `Node\Scalar\InterpolatedString`.
|
||||
* `Node\Scalar\EncapsedStringPart` is now `Node\InterpolatedStringPart` and no longer extends
|
||||
`Node\Scalar` or `Node\Expr`.
|
||||
* `Node\Expr\ArrayItem` is now `Node\ArrayItem` and no longer extends `Node\Expr`.
|
||||
* `Node\Expr\ClosureUse` is now `Node\ClosureUse` and no longer extends `Node\Expr`.
|
||||
* `Node\Stmt\DeclareDeclare` is now `Node\DeclareItem` and no longer extends `Node\Stmt`.
|
||||
* `Node\Stmt\PropertyProperty` is now `Node\PropertyItem` and no longer extends `Node\Stmt`.
|
||||
* `Node\Stmt\StaticVar` is now `Node\StaticVar` and no longer extends `Node\Stmt`.
|
||||
* `Node\Stmt\UseUse` is now `Node\UseItem` and no longer extends `Node\Stmt`.
|
||||
|
||||
The old class names have been retained as aliases for backwards compatibility. However, the `Node::getType()` method will now always return the new name (e.g. `ClosureUse` instead of `Expr_ClosureUse`).
|
||||
|
||||
### Modifiers
|
||||
|
||||
Modifier flags (as used by the `$flags` subnode of `Class_`, `ClassMethod`, `Property`, etc.) are now available as class constants on a separate `PhpParser\Modifiers` class, instead of being part of `PhpParser\Node\Stmt\Class_`, to make it clearer that these are used by many different nodes. The old constants are deprecated, but are still available.
|
||||
|
||||
```
|
||||
PhpParser\Node\Stmt\Class_::MODIFIER_PUBLIC -> PhpParser\Modifiers::PUBLIC
|
||||
PhpParser\Node\Stmt\Class_::MODIFIER_PROTECTED -> PhpParser\Modifiers::PROTECTED
|
||||
PhpParser\Node\Stmt\Class_::MODIFIER_PRIVATE -> PhpParser\Modifiers::PRIVATE
|
||||
PhpParser\Node\Stmt\Class_::MODIFIER_STATIC -> PhpParser\Modifiers::STATIC
|
||||
PhpParser\Node\Stmt\Class_::MODIFIER_ABSTRACT -> PhpParser\Modifiers::ABSTRACT
|
||||
PhpParser\Node\Stmt\Class_::MODIFIER_FINAL -> PhpParser\Modifiers::FINAL
|
||||
PhpParser\Node\Stmt\Class_::MODIFIER_READONLY -> PhpParser\Modifiers::READONLY
|
||||
PhpParser\Node\Stmt\Class_::VISIBILITY_MODIFIER_MASK -> PhpParser\Modifiers::VISIBILITY_MASK
|
||||
```
|
||||
|
||||
### Changes to the default pretty printer
|
||||
|
||||
A number of changes to the standard pretty printer have been made, to make it match contemporary coding style conventions (and in particular PSR-12). Options to restore the previous behavior are not provided, but it is possible to override the formatting methods (such as `pStmt_ClassMethod`) with your preferred formatting.
|
||||
|
||||
Return types are now formatted without a space before the `:`:
|
||||
|
||||
```php
|
||||
# Before
|
||||
function test() : Type
|
||||
{
|
||||
}
|
||||
|
||||
# After
|
||||
function test(): Type
|
||||
{
|
||||
}
|
||||
```
|
||||
|
||||
`abstract` and `final` are now printed before visibility modifiers:
|
||||
|
||||
```php
|
||||
# Before
|
||||
public abstract function test();
|
||||
|
||||
# After
|
||||
abstract public function test();
|
||||
```
|
||||
|
||||
A space is now printed between `use` and the following `(` for closures:
|
||||
|
||||
```php
|
||||
# Before
|
||||
function () use($var) {
|
||||
};
|
||||
|
||||
# After
|
||||
function () use ($var) {
|
||||
};
|
||||
```
|
||||
|
||||
Backslashes in single-quoted strings are now only printed if they are necessary:
|
||||
|
||||
```php
|
||||
# Before
|
||||
'Foo\\Bar';
|
||||
'\\\\';
|
||||
|
||||
# After
|
||||
'Foo\Bar';
|
||||
'\\\\';
|
||||
```
|
||||
|
||||
The pretty printer now accepts a `phpVersion` option, which accepts a `PhpVersion` object and defaults to PHP 7.0. The pretty printer will make formatting choices to make the code valid for that version. It currently controls the following behavior:
|
||||
|
||||
* For PHP >= 7.0 (default), short array syntax `[]` will be used by default. This does not affect nodes that specify an explicit array syntax using the `kind` attribute.
|
||||
* For PHP >= 7.1, the short array syntax `[]` will be used for destructuring by default (instead of
|
||||
`list()`). This does not affect nodes that specify and explicit syntax using the `kind` attribute.
|
||||
* For PHP >= 7.3, a newline is no longer forced after heredoc/nowdoc strings, as the requirement for this has been removed with the introduction of flexible heredoc/nowdoc strings.
|
||||
|
||||
### Changes to token representation
|
||||
|
||||
Tokens are now internally represented using the `PhpParser\Token` class, which exposes the same base interface as
|
||||
the `PhpToken` class introduced in PHP 8.0. On PHP 8.0 or newer, `PhpParser\Token` extends from `PhpToken`, otherwise
|
||||
it extends from a polyfill implementation. The most important parts of the interface may be summarized as follows:
|
||||
|
||||
```php
|
||||
class Token {
|
||||
public int $id;
|
||||
public string $text;
|
||||
public int $line;
|
||||
public int $pos;
|
||||
|
||||
public function is(int|string|array $kind): bool;
|
||||
}
|
||||
```
|
||||
|
||||
The `Lexer::getTokens()` method will now return an array of `Token`s, rather than an array of arrays and strings.
|
||||
Additionally, the token array is now terminated by a sentinel token with ID 0.
|
||||
|
||||
### Other removed functionality
|
||||
|
||||
* The deprecated `Builder\Param::setTypeHint()` method has been removed in favor of `Builder\Param::setType()`.
|
@ -26,13 +26,10 @@ if (empty($files)) {
|
||||
showHelp("Must specify at least one file.");
|
||||
}
|
||||
|
||||
$lexer = new PhpParser\Lexer\Emulative(['usedAttributes' => [
|
||||
$lexerOptions = ['usedAttributes' => [
|
||||
'startLine', 'endLine', 'startFilePos', 'endFilePos', 'comments'
|
||||
]]);
|
||||
$parser = (new PhpParser\ParserFactory)->create(
|
||||
PhpParser\ParserFactory::PREFER_PHP7,
|
||||
$lexer
|
||||
);
|
||||
]];
|
||||
$parser = (new PhpParser\ParserFactory())->createForVersion($attributes['version'], $lexerOptions);
|
||||
$dumper = new PhpParser\NodeDumper([
|
||||
'dumpComments' => true,
|
||||
'dumpPositions' => $attributes['with-positions'],
|
||||
@ -108,7 +105,7 @@ function showHelp($error = '') {
|
||||
if ($error) {
|
||||
fwrite(STDERR, $error . "\n\n");
|
||||
}
|
||||
fwrite($error ? STDERR : STDOUT, <<<OUTPUT
|
||||
fwrite($error ? STDERR : STDOUT, <<<'OUTPUT'
|
||||
Usage: php-parse [operations] file1.php [file2.php ...]
|
||||
or: php-parse [operations] "<?php code"
|
||||
Turn PHP source code into an abstract syntax tree.
|
||||
@ -123,6 +120,7 @@ Operations is a list of the following options (--dump by default):
|
||||
-c, --with-column-info Show column-numbers for errors (if available)
|
||||
-P, --with-positions Show positions in node dumps
|
||||
-r, --with-recovery Use parsing with error recovery
|
||||
--version=VERSION Target specific PHP version (default: newest)
|
||||
-h, --help Display this page
|
||||
|
||||
Example:
|
||||
@ -143,6 +141,7 @@ function parseArgs($args) {
|
||||
'with-column-info' => false,
|
||||
'with-positions' => false,
|
||||
'with-recovery' => false,
|
||||
'version' => PhpParser\PhpVersion::getNewestSupported(),
|
||||
];
|
||||
|
||||
array_shift($args);
|
||||
@ -193,7 +192,9 @@ function parseArgs($args) {
|
||||
$parseOptions = false;
|
||||
break;
|
||||
default:
|
||||
if ($arg[0] === '-') {
|
||||
if (preg_match('/^--version=(.*)$/', $arg, $matches)) {
|
||||
$attributes['version'] = PhpParser\PhpVersion::fromString($matches[1]);
|
||||
} elseif ($arg[0] === '-') {
|
||||
showHelp("Invalid operation $arg.");
|
||||
} else {
|
||||
$files[] = $arg;
|
||||
|
@ -13,8 +13,10 @@
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": ">=7.0",
|
||||
"ext-tokenizer": "*"
|
||||
"php": ">=7.1",
|
||||
"ext-tokenizer": "*",
|
||||
"ext-json": "*",
|
||||
"ext-ctype": "*"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^6.5 || ^7.0 || ^8.0 || ^9.0",
|
||||
@ -22,7 +24,7 @@
|
||||
},
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "4.9-dev"
|
||||
"dev-master": "5.0-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
|
@ -1,7 +1,7 @@
|
||||
Introduction
|
||||
============
|
||||
|
||||
This project is a PHP 5.2 to PHP 8.0 parser **written in PHP itself**.
|
||||
This project is a PHP parser **written in PHP itself**.
|
||||
|
||||
What is this for?
|
||||
-----------------
|
||||
@ -26,16 +26,29 @@ programmatic PHP code analysis are incidentally PHP developers, not C developers
|
||||
What can it parse?
|
||||
------------------
|
||||
|
||||
The parser supports parsing PHP 5.2-8.0, with the following exceptions:
|
||||
The parser supports parsing PHP 7 and PHP 8 code, 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
|
||||
These are illegal in PHP 8, but are legal in earlier versions. However, PHP-Parser does not
|
||||
support them for any version.
|
||||
|
||||
PHP-Parser 4.x had full support for parsing PHP 5. PHP-Parser 5.x has only limited support, with the
|
||||
following caveats:
|
||||
|
||||
* Some variable expressions like `$$foo[0]` are valid in both PHP 5 and PHP 7, but have different
|
||||
interpretation. In such cases, the PHP 7 AST will always be constructed (using `($$foo)[0]`
|
||||
rather than `${$foo[0]}`).
|
||||
* Declarations of the form `global $$var[0]` are not supported in PHP 7 and will cause a parse
|
||||
error. In error recovery mode, it is possible to continue parsing after such declarations.
|
||||
|
||||
As the parser is based on the tokens returned by `token_get_all` (which is only able to lex the PHP
|
||||
version it runs on), additionally a wrapper for emulating tokens from newer versions is provided.
|
||||
This allows to parse PHP 7.4 source code running on PHP 7.0, for example. This emulation is somewhat
|
||||
hacky and not perfect, but it should work well on any sane code.
|
||||
This allows to parse PHP 8.0 source code running on PHP 7.1, for example. This emulation is not
|
||||
perfect, but works well in practice.
|
||||
|
||||
Finally, it should be noted that the parser aims to accept all valid code, not reject all invalid
|
||||
code. It will generally accept code that is only valid in newer versions (even when targeting an
|
||||
older one), and accept code that is syntactically correct, but would result in a compiler error.
|
||||
|
||||
What output does it produce?
|
||||
----------------------------
|
||||
@ -63,7 +76,7 @@ This matches the structure of the code: An echo statement, which takes two strin
|
||||
with the values `Hi` and `World`.
|
||||
|
||||
You can also see that the AST does not contain any whitespace information (but most comments are saved).
|
||||
So using it for formatting analysis is not possible.
|
||||
However, it does retain accurate position information, which can be used to inspect precise formatting.
|
||||
|
||||
What else can it do?
|
||||
--------------------
|
||||
@ -74,7 +87,7 @@ Apart from the parser itself this package also bundles support for some other, r
|
||||
that "pretty printing" does not imply that the output is especially pretty. It's just how it's
|
||||
called ;)
|
||||
* Support for serializing and unserializing the node tree to JSON
|
||||
* Support for dumping the node tree in a human readable form (see the section above for an
|
||||
* Support for dumping the node tree in a human-readable form (see the section above for an
|
||||
example of how the output looks like)
|
||||
* Infrastructure for traversing and changing the AST (node traverser and node visitors)
|
||||
* A node visitor for resolving namespaced names
|
||||
|
@ -12,7 +12,7 @@ To bootstrap the library, include the autoloader generated by composer:
|
||||
require 'path/to/vendor/autoload.php';
|
||||
```
|
||||
|
||||
Additionally you may want to set the `xdebug.max_nesting_level` ini option to a higher value:
|
||||
Additionally, you may want to set the `xdebug.max_nesting_level` ini option to a higher value:
|
||||
|
||||
```php
|
||||
ini_set('xdebug.max_nesting_level', 3000);
|
||||
@ -29,25 +29,29 @@ In order to parse code, you first have to create a parser instance:
|
||||
|
||||
```php
|
||||
use PhpParser\ParserFactory;
|
||||
$parser = (new ParserFactory)->create(ParserFactory::PREFER_PHP7);
|
||||
use PhpParser\PhpVersion;
|
||||
|
||||
// Parser for the version you are running on.
|
||||
$parser = (new ParserFactory())->createForHostVersion();
|
||||
|
||||
// Parser for the newest PHP version supported by the PHP-Parser library.
|
||||
$parser = (new ParserFactory())->createForNewestSupportedVersion();
|
||||
|
||||
// Parser for a specific PHP version.
|
||||
$parser = (new ParserFactory())->createForVersion(PhpVersion::fromString('8.1'));
|
||||
```
|
||||
|
||||
The factory accepts a kind argument, that determines how different PHP versions are treated:
|
||||
Which version you should target depends on your use case. In many cases you will want to use the
|
||||
host version, as people typically analyze code for the version they are running on. However, when
|
||||
analyzing arbitrary code you are usually best off using the newest supported version, which tends
|
||||
to accept the widest range of code (unless there are breaking changes in PHP).
|
||||
|
||||
Kind | Behavior
|
||||
-----|---------
|
||||
`ParserFactory::PREFER_PHP7` | Try to parse code as PHP 7. If this fails, try to parse it as PHP 5.
|
||||
`ParserFactory::PREFER_PHP5` | Try to parse code as PHP 5. If this fails, try to parse it as PHP 7.
|
||||
`ParserFactory::ONLY_PHP7` | Parse code as PHP 7.
|
||||
`ParserFactory::ONLY_PHP5` | Parse code as PHP 5.
|
||||
The `createXYZ()` methods optionally accept an array of lexer options. Some use cases that require
|
||||
customized lexer options are discussed in the [lexer documentation](component/Lexer.markdown).
|
||||
|
||||
Unless you have a strong reason to use something else, `PREFER_PHP7` is a reasonable default.
|
||||
|
||||
The `create()` method optionally accepts a `Lexer` instance as the second argument. Some use cases
|
||||
that require customized lexers are discussed in the [lexer documentation](component/Lexer.markdown).
|
||||
|
||||
Subsequently you can pass PHP code (including the opening `<?php` tag) to the `parse` method in order to
|
||||
create a syntax tree. If a syntax error is encountered, an `PhpParser\Error` exception will be thrown:
|
||||
Subsequently, you can pass PHP code (including the opening `<?php` tag) to the `parse()` method in
|
||||
order to create a syntax tree. If a syntax error is encountered, an `PhpParser\Error` exception will
|
||||
be thrown by default:
|
||||
|
||||
```php
|
||||
<?php
|
||||
@ -62,7 +66,7 @@ function printLine($msg) {
|
||||
printLine('Hello World!!!');
|
||||
CODE;
|
||||
|
||||
$parser = (new ParserFactory)->create(ParserFactory::PREFER_PHP7);
|
||||
$parser = (new ParserFactory())->createForHostVersion();
|
||||
|
||||
try {
|
||||
$stmts = $parser->parse($code);
|
||||
@ -77,7 +81,7 @@ A parser instance can be reused to parse multiple files.
|
||||
Node dumping
|
||||
------------
|
||||
|
||||
To dump the abstract syntax tree in human readable form, a `NodeDumper` can be used:
|
||||
To dump the abstract syntax tree in human-readable form, a `NodeDumper` can be used:
|
||||
|
||||
```php
|
||||
<?php
|
||||
@ -171,7 +175,7 @@ with them easier they are grouped into three categories:
|
||||
|
||||
* `PhpParser\Node\Stmt`s are statement nodes, i.e. language constructs that do not return
|
||||
a value and can not occur in an expression. For example a class definition is a statement.
|
||||
It doesn't return a value and you can't write something like `func(class A {});`.
|
||||
It doesn't return a value, and you can't write something like `func(class A {});`.
|
||||
* `PhpParser\Node\Expr`s are expression nodes, i.e. language constructs that return a value
|
||||
and thus can occur in other expressions. Examples of expressions are `$var`
|
||||
(`PhpParser\Node\Expr\Variable`) and `func()` (`PhpParser\Node\Expr\FuncCall`).
|
||||
@ -201,15 +205,14 @@ can then be retrieved using `hasAttribute()`, `getAttribute()` and `getAttribute
|
||||
By default the lexer adds the `startLine`, `endLine` and `comments` attributes. `comments` is an array
|
||||
of `PhpParser\Comment[\Doc]` instances.
|
||||
|
||||
The start line can also be accessed using `getLine()`/`setLine()` (instead of `getAttribute('startLine')`).
|
||||
The start line can also be accessed using `getStartLine()` (instead of `getAttribute('startLine')`).
|
||||
The last doc comment from the `comments` attribute can be obtained using `getDocComment()`.
|
||||
|
||||
Pretty printer
|
||||
--------------
|
||||
|
||||
The pretty printer component compiles the AST back to PHP code. As the parser does not retain formatting
|
||||
information the formatting is done using a specified scheme. Currently there is only one scheme available,
|
||||
namely `PhpParser\PrettyPrinter\Standard`.
|
||||
The pretty printer component compiles the AST back to PHP code according to a specified scheme.
|
||||
Currently, there is only one scheme available, namely `PhpParser\PrettyPrinter\Standard`.
|
||||
|
||||
```php
|
||||
use PhpParser\Error;
|
||||
@ -218,8 +221,8 @@ use PhpParser\PrettyPrinter;
|
||||
|
||||
$code = "<?php echo 'Hi ', hi\\getTarget();";
|
||||
|
||||
$parser = (new ParserFactory)->create(ParserFactory::PREFER_PHP7);
|
||||
$prettyPrinter = new PrettyPrinter\Standard;
|
||||
$parser = (new ParserFactory())->createForHostVersion();
|
||||
$prettyPrinter = new PrettyPrinter\Standard();
|
||||
|
||||
try {
|
||||
// parse
|
||||
@ -254,10 +257,13 @@ single expression using `prettyPrintExpr()`.
|
||||
The `prettyPrintFile()` method can be used to print an entire file. This will include the opening `<?php` tag
|
||||
and handle inline HTML as the first/last statement more gracefully.
|
||||
|
||||
There is also a pretty-printing mode which retains formatting for parts of the AST that have not
|
||||
been changed, which requires additional setup.
|
||||
|
||||
> Read more: [Pretty printing documentation](component/Pretty_printing.markdown)
|
||||
|
||||
Node traversation
|
||||
-----------------
|
||||
Node traversal
|
||||
--------------
|
||||
|
||||
The above pretty printing example used the fact that the source code was known and thus it was easy to
|
||||
write code that accesses a certain part of a node tree and changes it. Normally this is not the case.
|
||||
@ -272,7 +278,7 @@ use PhpParser\NodeTraverser;
|
||||
use PhpParser\ParserFactory;
|
||||
use PhpParser\PrettyPrinter;
|
||||
|
||||
$parser = (new ParserFactory)->create(ParserFactory::PREFER_PHP7);
|
||||
$parser = (new ParserFactory())->createForHostVersion();
|
||||
$traverser = new NodeTraverser;
|
||||
$prettyPrinter = new PrettyPrinter\Standard;
|
||||
|
||||
@ -303,8 +309,7 @@ The corresponding node visitor might look like this:
|
||||
use PhpParser\Node;
|
||||
use PhpParser\NodeVisitorAbstract;
|
||||
|
||||
class MyNodeVisitor extends NodeVisitorAbstract
|
||||
{
|
||||
class MyNodeVisitor extends NodeVisitorAbstract {
|
||||
public function leaveNode(Node $node) {
|
||||
if ($node instanceof Node\Scalar\String_) {
|
||||
$node->value = 'foo';
|
||||
@ -326,7 +331,7 @@ public function afterTraverse(array $nodes);
|
||||
```
|
||||
|
||||
The `beforeTraverse()` method is called once before the traversal begins and is passed the nodes the
|
||||
traverser was called with. This method can be used for resetting values before traversation or
|
||||
traverser was called with. This method can be used for resetting values before traversal or
|
||||
preparing the tree for traversal.
|
||||
|
||||
The `afterTraverse()` method is similar to the `beforeTraverse()` method, with the only difference that
|
||||
@ -342,8 +347,8 @@ The `enterNode()` method can additionally return the value `NodeTraverser::DONT_
|
||||
which instructs the traverser to skip all children of the current node. To furthermore prevent subsequent
|
||||
visitors from visiting the current node, `NodeTraverser::DONT_TRAVERSE_CURRENT_AND_CHILDREN` can be used instead.
|
||||
|
||||
The `leaveNode()` method can additionally return the value `NodeTraverser::REMOVE_NODE`, in which
|
||||
case the current node will be removed from the parent array. Furthermore it is possible to return
|
||||
Both methods can additionally return the value `NodeTraverser::REMOVE_NODE`, in which
|
||||
case the current node will be removed from the parent array. Furthermore, it is possible to return
|
||||
an array of nodes, which will be merged into the parent array at the offset of the current node.
|
||||
I.e. if in `array(A, B, C)` the node `B` should be replaced with `array(X, Y, Z)` the result will
|
||||
be `array(A, X, Y, Z, C)`.
|
||||
@ -372,8 +377,9 @@ unqualified function and constant names. These are resolved at runtime and thus
|
||||
know which function they are referring to. In most cases this is a non-issue as the global functions
|
||||
are meant.
|
||||
|
||||
Also the `NameResolver` adds a `namespacedName` subnode to class, function and constant declarations
|
||||
that contains the namespaced name instead of only the shortname that is available via `name`.
|
||||
Additionally, the `NameResolver` adds a `namespacedName` subnode to class, function and constant
|
||||
declarations that contains the namespaced name instead of only the shortname that is available via
|
||||
`name`.
|
||||
|
||||
> Read more: [Name resolution documentation](component/Name_resolution.markdown)
|
||||
|
||||
@ -396,7 +402,7 @@ use PhpParser\NodeVisitor\NameResolver;
|
||||
$inDir = '/some/path';
|
||||
$outDir = '/some/other/path';
|
||||
|
||||
$parser = (new ParserFactory)->create(ParserFactory::PREFER_PHP7);
|
||||
$parser = (new ParserFactory())->createForNewestSupportedVersion();
|
||||
$traverser = new NodeTraverser;
|
||||
$prettyPrinter = new PrettyPrinter\Standard;
|
||||
|
||||
|
@ -101,7 +101,7 @@ abstract class SomeOtherClass extends SomeClass implements A\Few, \Interfaces
|
||||
*
|
||||
* @param SomeClass And takes a parameter
|
||||
*/
|
||||
public abstract function someMethod(SomeClass $someParam) : bool;
|
||||
public abstract function someMethod(SomeClass $someParam): bool;
|
||||
protected function anotherMethod($someParam = 'test')
|
||||
{
|
||||
print $someParam;
|
||||
|
@ -19,9 +19,9 @@ PHP-Parser supports evaluation of such constant expressions through the `ConstEx
|
||||
|
||||
use PhpParser\{ConstExprEvaluator, ConstExprEvaluationException};
|
||||
|
||||
$evalutator = new ConstExprEvaluator();
|
||||
$evaluator = new ConstExprEvaluator();
|
||||
try {
|
||||
$value = $evalutator->evaluateSilently($someExpr);
|
||||
$value = $evaluator->evaluateSilently($someExpr);
|
||||
} catch (ConstExprEvaluationException $e) {
|
||||
// Either the expression contains unsupported expression types,
|
||||
// or an error occurred during evaluation
|
||||
@ -45,7 +45,7 @@ use PhpParser\Node\{Expr, Scalar};
|
||||
$evaluator = new ConstExprEvaluator();
|
||||
|
||||
// 10 / 0
|
||||
$expr = new Expr\BinaryOp\Div(new Scalar\LNumber(10), new Scalar\LNumber(0));
|
||||
$expr = new Expr\BinaryOp\Div(new Scalar\Int_(10), new Scalar\Int_(0));
|
||||
|
||||
var_dump($evaluator->evaluateDirectly($expr)); // float(INF)
|
||||
// Warning: Division by zero
|
||||
@ -69,6 +69,7 @@ expressions, apart from the following:
|
||||
* `Scalar\MagicConst\*`
|
||||
* `Expr\ConstFetch` (only null/false/true are handled)
|
||||
* `Expr\ClassConstFetch`
|
||||
* `Expr\New_` (since PHP 8.1)
|
||||
|
||||
Handling these expression types requires non-local information, such as which global constants are
|
||||
defined. By default, the evaluator will throw a `ConstExprEvaluationException` when it encounters
|
||||
@ -112,4 +113,4 @@ class Test {
|
||||
const A = self::B;
|
||||
const B = self::A;
|
||||
}
|
||||
```
|
||||
```
|
||||
|
@ -14,10 +14,10 @@ In order to receive information about not only the line, but also the column spa
|
||||
position attributes in the lexer need to be enabled:
|
||||
|
||||
```php
|
||||
$lexer = new PhpParser\Lexer(array(
|
||||
$lexerOptions = array(
|
||||
'usedAttributes' => array('comments', 'startLine', 'endLine', 'startFilePos', 'endFilePos'),
|
||||
));
|
||||
$parser = (new PhpParser\ParserFactory)->create(PhpParser\ParserFactory::PREFER_PHP7, $lexer);
|
||||
);
|
||||
$parser = (new PhpParser\ParserFactory())->createForHostVersion($lexerOptions);
|
||||
|
||||
try {
|
||||
$stmts = $parser->parse($code);
|
||||
@ -56,7 +56,7 @@ To instead collect all encountered errors into an array, while trying to continu
|
||||
an instance of `ErrorHandler\Collecting` can be passed to the `Parser::parse()` method. A usage example:
|
||||
|
||||
```php
|
||||
$parser = (new PhpParser\ParserFactory)->create(PhpParser\ParserFactory::ONLY_PHP7);
|
||||
$parser = (new PhpParser\ParserFactory())->createForHostVersion();
|
||||
$errorHandler = new PhpParser\ErrorHandler\Collecting;
|
||||
|
||||
$stmts = $parser->parse($code, $errorHandler);
|
||||
@ -72,4 +72,6 @@ if (null !== $stmts) {
|
||||
}
|
||||
```
|
||||
|
||||
The partial AST may contain `Expr\Error` nodes that indicate that an error occurred while parsing an expression.
|
||||
|
||||
The `NameResolver` visitor also accepts an `ErrorHandler` as a constructor argument.
|
||||
|
@ -19,7 +19,7 @@ $code = '...';
|
||||
$traverser = new NodeTraverser;
|
||||
$traverser->addVisitor(new ParentConnectingVisitor);
|
||||
|
||||
$parser = (new ParserFactory)->create(ParserFactory::PREFER_PHP7);
|
||||
$parser = (new ParserFactory())->createForHostVersion();
|
||||
$ast = $parser->parse($code);
|
||||
$ast = $traverser->traverse($ast);
|
||||
```
|
||||
@ -42,7 +42,7 @@ $code = '...';
|
||||
$traverser = new NodeTraverser;
|
||||
$traverser->addVisitor(new NodeConnectingVisitor);
|
||||
|
||||
$parser = (new ParserFactory)->create(ParserFactory::PREFER_PHP7);
|
||||
$parser = (new ParserFactory())->createForHostVersion();
|
||||
$ast = $parser->parse($code);
|
||||
$ast = $traverser->traverse($ast);
|
||||
```
|
||||
|
@ -18,7 +18,7 @@ function printLine($msg) {
|
||||
}
|
||||
CODE;
|
||||
|
||||
$parser = (new ParserFactory)->create(ParserFactory::PREFER_PHP7);
|
||||
$parser = (new ParserFactory())->createForHostVersion();
|
||||
|
||||
try {
|
||||
$stmts = $parser->parse($code);
|
||||
|
@ -42,16 +42,17 @@ The attributes used in this example match the default behavior of the lexer. The
|
||||
> **Note:** The example in this section is outdated in that this information is directly available in the AST: While
|
||||
> `$property->isPublic()` does not distinguish between `public` and `var`, directly checking `$property->flags` for
|
||||
> the `$property->flags & Class_::VISIBILITY_MODIFIER_MASK) === 0` allows making this distinction without resorting to
|
||||
> tokens. However the general idea behind the example still applies in other cases.
|
||||
> tokens. However, the general idea behind the example still applies in other cases.
|
||||
|
||||
The token offset information is useful if you wish to examine the exact formatting used for a node. For example the AST
|
||||
does not distinguish whether a property was declared using `public` or using `var`, but you can retrieve this
|
||||
information based on the token position:
|
||||
|
||||
```php
|
||||
/** @param PhpParser\Token[] $tokens */
|
||||
function isDeclaredUsingVar(array $tokens, PhpParser\Node\Stmt\Property $prop) {
|
||||
$i = $prop->getAttribute('startTokenPos');
|
||||
return $tokens[$i][0] === T_VAR;
|
||||
$i = $prop->getStartTokenPos();
|
||||
return $tokens[$i]->id === T_VAR;
|
||||
}
|
||||
```
|
||||
|
||||
@ -72,12 +73,12 @@ class MyNodeVisitor extends PhpParser\NodeVisitorAbstract {
|
||||
}
|
||||
}
|
||||
|
||||
$lexer = new PhpParser\Lexer(array(
|
||||
$lexerOptions = array(
|
||||
'usedAttributes' => array(
|
||||
'comments', 'startLine', 'endLine', 'startTokenPos', 'endTokenPos'
|
||||
)
|
||||
));
|
||||
$parser = (new PhpParser\ParserFactory)->create(PhpParser\ParserFactory::ONLY_PHP7, $lexer);
|
||||
);
|
||||
$parser = (new PhpParser\ParserFactory())->createForHostVersion($lexerOptions);
|
||||
|
||||
$visitor = new MyNodeVisitor();
|
||||
$traverser = new PhpParser\NodeTraverser();
|
||||
@ -111,14 +112,15 @@ The `startLexing()` method is invoked whenever the `parse()` method of the parse
|
||||
code that is to be lexed (including the opening tag). It can be used to reset state or preprocess the source code or tokens. The
|
||||
passed `ErrorHandler` should be used to report lexing errors.
|
||||
|
||||
The `getTokens()` method returns the current token array, in the usual `token_get_all()` format. This method is not
|
||||
used by the parser (which uses `getNextToken()`), but is useful in combination with the token position attributes.
|
||||
The `getTokens()` method returns the current array of `PhpParser\Token`s, which are compatible with the PHP 8 `PhpToken`
|
||||
class. This method is not used by the parser (which uses `getNextToken()`), but is useful in combination with the token
|
||||
position attributes.
|
||||
|
||||
The `handleHaltCompiler()` method is called whenever a `T_HALT_COMPILER` token is encountered. It has to return the
|
||||
remaining string after the construct (not including `();`).
|
||||
|
||||
The `getNextToken()` method returns the ID of the next token (as defined by the `Parser::T_*` constants). If no more
|
||||
tokens are available it must return `0`, which is the ID of the `EOF` token. Furthermore the string content of the
|
||||
The `getNextToken()` method returns the ID of the next token (in the sense of `Token::$id`). If no more
|
||||
tokens are available it must return `0`, which is the ID of the `EOF` token. Furthermore, the string content of the
|
||||
token should be written into the by-reference `$value` parameter (which will then be available as `$n` in the parser).
|
||||
|
||||
### Attribute handling
|
||||
@ -144,10 +146,10 @@ class KeepOriginalValueLexer extends Lexer // or Lexer\Emulative
|
||||
public function getNextToken(&$value = null, &$startAttributes = null, &$endAttributes = null) {
|
||||
$tokenId = parent::getNextToken($value, $startAttributes, $endAttributes);
|
||||
|
||||
if ($tokenId == Tokens::T_CONSTANT_ENCAPSED_STRING // non-interpolated string
|
||||
|| $tokenId == Tokens::T_ENCAPSED_AND_WHITESPACE // interpolated string
|
||||
|| $tokenId == Tokens::T_LNUMBER // integer
|
||||
|| $tokenId == Tokens::T_DNUMBER // floating point number
|
||||
if ($tokenId == \T_CONSTANT_ENCAPSED_STRING // non-interpolated string
|
||||
|| $tokenId == \T_ENCAPSED_AND_WHITESPACE // interpolated string
|
||||
|| $tokenId == \T_LNUMBER // integer
|
||||
|| $tokenId == \T_DNUMBER // floating point number
|
||||
) {
|
||||
// could also use $startAttributes, doesn't really matter here
|
||||
$endAttributes['originalValue'] = $value;
|
||||
|
@ -10,7 +10,7 @@ visitor (NameResolver) based on it.
|
||||
The NameResolver visitor
|
||||
------------------------
|
||||
|
||||
The `NameResolver` visitor can (and for nearly all uses of the AST, is) be applied to resolve names
|
||||
The `NameResolver` visitor can (and for nearly all uses of the AST, should) be applied to resolve names
|
||||
to their fully-qualified form, to the degree that this is possible.
|
||||
|
||||
```php
|
||||
@ -53,7 +53,7 @@ name. Once again, if an unqualified function or constant name cannot be resolved
|
||||
`resolvedName` attribute will not be present, and instead a `namespacedName` attribute is added.
|
||||
|
||||
The `replaceNodes` attribute is useful if you wish to perform modifications on the AST, as you
|
||||
probably do not wish the resoluting code to have fully resolved names as a side-effect.
|
||||
probably do not wish the resulting code to have fully resolved names as a side-effect.
|
||||
|
||||
The NameContext
|
||||
---------------
|
||||
@ -84,4 +84,4 @@ representation of a name given the current name resolution environment.
|
||||
The name context is intended to be used for name resolution operations outside the AST itself, such
|
||||
as class names inside doc comments. A visitor running in parallel with the name resolver can access
|
||||
the name context using `$nameResolver->getNameContext()`. Alternatively a visitor can use an
|
||||
independent context and explicitly feed `Namespace` and `Use` nodes to it.
|
||||
independent context and explicitly feed `Namespace` and `Use` nodes to it.
|
||||
|
@ -31,10 +31,16 @@ expression.
|
||||
Customizing the formatting
|
||||
--------------------------
|
||||
|
||||
Apart from an `shortArraySyntax` option, the default pretty printer does not provide any
|
||||
functionality to customize the formatting of the generated code. The pretty printer does respect a
|
||||
number of `kind` attributes used by some notes (e.g., whether an integer should be printed as
|
||||
decimal, hexadecimal, etc), but there are no options to control brace placement or similar.
|
||||
The pretty printer respects a number of `kind` attributes used by some notes (e.g., whether an
|
||||
integer should be printed as decimal, hexadecimal, etc). Additionally, it supports two options:
|
||||
|
||||
* `phpVersion` (defaults to 7.0) allows opting into formatting that is not supported by older PHP
|
||||
versions.
|
||||
* `shortArraySyntax` determines the used array syntax if the `kind` attribute is not set. This is
|
||||
a legacy option, and `phpVersion` should be used to control this behavior instead.
|
||||
|
||||
However, the default pretty printer does not provide any functionality for fine-grained
|
||||
customization of code formatting.
|
||||
|
||||
If you want to make minor changes to the formatting, the easiest way is to extend the pretty printer
|
||||
and override the methods responsible for the node types you are interested in.
|
||||
@ -46,29 +52,27 @@ default pretty printer with an existing library for code reformatting, such as
|
||||
Formatting-preserving pretty printing
|
||||
-------------------------------------
|
||||
|
||||
> **Note:** This functionality is **experimental** and not yet complete.
|
||||
|
||||
For automated code refactoring, migration and similar, you will usually only want to modify a small
|
||||
portion of the code and leave the remainder alone. The basic pretty printer is not suitable for
|
||||
this, because it will also reformat parts of the code which have not been modified.
|
||||
|
||||
Since PHP-Parser 4.0, an experimental formatting-preserving pretty-printing mode is available, which
|
||||
Since PHP-Parser 4.0, a formatting-preserving pretty-printing mode is available, which
|
||||
attempts to preserve the formatting of code (those AST nodes that have not changed) and only reformat
|
||||
code which has been modified or newly inserted.
|
||||
|
||||
Use of the formatting-preservation functionality requires some additional preparatory steps:
|
||||
|
||||
```php
|
||||
use PhpParser\{Lexer, NodeTraverser, NodeVisitor, Parser, PrettyPrinter};
|
||||
use PhpParser\{Lexer, NodeTraverser, NodeVisitor, ParserFactory, PrettyPrinter};
|
||||
|
||||
$lexer = new Lexer\Emulative([
|
||||
$lexerOptions = new [
|
||||
'usedAttributes' => [
|
||||
'comments',
|
||||
'startLine', 'endLine',
|
||||
'startTokenPos', 'endTokenPos',
|
||||
],
|
||||
]);
|
||||
$parser = new Parser\Php7($lexer);
|
||||
];
|
||||
$parser = (new ParserFactory())->createForHostVersion($lexerOptions);
|
||||
|
||||
$traverser = new NodeTraverser();
|
||||
$traverser->addVisitor(new NodeVisitor\CloningVisitor());
|
||||
@ -86,11 +90,9 @@ $newCode = $printer->printFormatPreserving($newStmts, $oldStmts, $oldTokens);
|
||||
```
|
||||
|
||||
If you make use of the name resolution functionality, you will likely want to disable the
|
||||
`replaceNodes` option. This will add resolved names as attributes, instead of directlying modifying
|
||||
`replaceNodes` option. This will add resolved names as attributes, instead of directly modifying
|
||||
the AST and causing spurious changes to the pretty printed code. For more information, see the
|
||||
[name resolution documentation](Name_resolution.markdown).
|
||||
|
||||
This functionality is experimental and not yet fully implemented. It should not provide incorrect
|
||||
code, but it may sometimes reformat more code than necessary. Open issues are tracked in
|
||||
[issue #344](https://github.com/nikic/PHP-Parser/issues/344). If you encounter problems while using
|
||||
this functionality, please open an issue, so we know what to prioritize.
|
||||
The formatting-preservation works on a best-effort basis and may sometimes reformat more code tha
|
||||
necessary. If you encounter problems while using this functionality, please open an issue.
|
||||
|
@ -11,7 +11,7 @@ use PhpParser\{Node, NodeTraverser, NodeVisitorAbstract};
|
||||
$traverser = new NodeTraverser;
|
||||
$traverser->addVisitor(new class extends NodeVisitorAbstract {
|
||||
public function leaveNode(Node $node) {
|
||||
if ($node instanceof Node\Scalar\LNumber) {
|
||||
if ($node instanceof Node\Scalar\Int_) {
|
||||
return new Node\Scalar\String_((string) $node->value);
|
||||
}
|
||||
}
|
||||
@ -129,8 +129,7 @@ Now `$a && $b` will be replaced by `!($a && $b)`. Then the traverser will go int
|
||||
only) child of `!($a && $b)`, which is `$a && $b`. The transformation applies again and we end up
|
||||
with `!!($a && $b)`. This will continue until PHP hits the memory limit.
|
||||
|
||||
Finally, two special replacement types are supported only by leaveNode. The first is removal of a
|
||||
node:
|
||||
Finally, there are two special replacement types. The first is removal of a node:
|
||||
|
||||
```php
|
||||
public function leaveNode(Node $node) {
|
||||
@ -165,8 +164,8 @@ This example will remove all calls to `var_dump()` which occur as expression sta
|
||||
that `var_dump($a);` will be removed, but `if (var_dump($a))` will not be removed (and there is no
|
||||
obvious way in which it can be removed).
|
||||
|
||||
Next to removing nodes, it is also possible to replace one node with multiple nodes. Again, this
|
||||
only works inside leaveNode and only if the parent structure is an array.
|
||||
Next to removing nodes, it is also possible to replace one node with multiple nodes. This
|
||||
only works if the parent structure is an array.
|
||||
|
||||
```php
|
||||
public function leaveNode(Node $node) {
|
||||
|
@ -1,11 +1,8 @@
|
||||
What do all those files mean?
|
||||
=============================
|
||||
|
||||
* `php5.y`: PHP 5 grammar written in a pseudo language
|
||||
* `php7.y`: PHP 7 grammar written in a pseudo language
|
||||
* `tokens.y`: Tokens definition shared between PHP 5 and PHP 7 grammars
|
||||
* `php.y`: PHP 5-8 grammar written in a pseudo language
|
||||
* `parser.template`: A `kmyacc` parser prototype file for PHP
|
||||
* `tokens.template`: A `kmyacc` prototype file for the `Tokens` class
|
||||
* `rebuildParsers.php`: Preprocesses the grammar and builds the parser using `kmyacc`
|
||||
|
||||
.phpy pseudo language
|
||||
|
@ -1,4 +1,4 @@
|
||||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
$meta #
|
||||
#semval($) $this->semValue
|
||||
#semval($,%t) $this->semValue
|
||||
@ -8,6 +8,7 @@ $meta #
|
||||
namespace PhpParser\Parser;
|
||||
|
||||
use PhpParser\Error;
|
||||
use PhpParser\Modifiers;
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Expr;
|
||||
use PhpParser\Node\Name;
|
||||
@ -17,12 +18,16 @@ use PhpParser\Node\Stmt;
|
||||
|
||||
/* This is an automatically GENERATED file, which should not be manually edited.
|
||||
* Instead edit one of the following:
|
||||
* * the grammar files grammar/php5.y or grammar/php7.y
|
||||
* * the grammar file grammar/php.y
|
||||
* * the skeleton file grammar/parser.template
|
||||
* * the preprocessing script grammar/rebuildParsers.php
|
||||
*/
|
||||
class #(-p) extends \PhpParser\ParserAbstract
|
||||
{
|
||||
#tokenval
|
||||
public const %s = %n;
|
||||
#endtokenval
|
||||
|
||||
protected $tokenToSymbolMapSize = #(YYMAXLEX);
|
||||
protected $actionTableSize = #(YYLAST);
|
||||
protected $gotoTableSize = #(YYGLAST);
|
||||
|
@ -1,7 +1,125 @@
|
||||
%pure_parser
|
||||
%expect 2
|
||||
|
||||
%tokens
|
||||
%right T_THROW
|
||||
%left T_INCLUDE T_INCLUDE_ONCE T_EVAL T_REQUIRE T_REQUIRE_ONCE
|
||||
%left ','
|
||||
%left T_LOGICAL_OR
|
||||
%left T_LOGICAL_XOR
|
||||
%left T_LOGICAL_AND
|
||||
%right T_PRINT
|
||||
%right T_YIELD
|
||||
%right T_DOUBLE_ARROW
|
||||
%right T_YIELD_FROM
|
||||
%left '=' T_PLUS_EQUAL T_MINUS_EQUAL T_MUL_EQUAL T_DIV_EQUAL T_CONCAT_EQUAL T_MOD_EQUAL T_AND_EQUAL T_OR_EQUAL T_XOR_EQUAL T_SL_EQUAL T_SR_EQUAL T_POW_EQUAL T_COALESCE_EQUAL
|
||||
%left '?' ':'
|
||||
%right T_COALESCE
|
||||
%left T_BOOLEAN_OR
|
||||
%left T_BOOLEAN_AND
|
||||
%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
|
||||
#if PHP7
|
||||
%left T_SL T_SR
|
||||
%left '+' '-' '.'
|
||||
#endif
|
||||
#if PHP8
|
||||
%left '.'
|
||||
%left T_SL T_SR
|
||||
%left '+' '-'
|
||||
#endif
|
||||
%left '*' '/' '%'
|
||||
%right '!'
|
||||
%nonassoc T_INSTANCEOF
|
||||
%right '~' T_INC T_DEC T_INT_CAST T_DOUBLE_CAST T_STRING_CAST T_ARRAY_CAST T_OBJECT_CAST T_BOOL_CAST T_UNSET_CAST '@'
|
||||
%right T_POW
|
||||
%right '['
|
||||
%nonassoc T_NEW T_CLONE
|
||||
%token T_EXIT
|
||||
%token T_IF
|
||||
%left T_ELSEIF
|
||||
%left T_ELSE
|
||||
%left T_ENDIF
|
||||
%token T_LNUMBER
|
||||
%token T_DNUMBER
|
||||
%token T_STRING
|
||||
%token T_STRING_VARNAME
|
||||
%token T_VARIABLE
|
||||
%token T_NUM_STRING
|
||||
%token T_INLINE_HTML
|
||||
%token T_ENCAPSED_AND_WHITESPACE
|
||||
%token T_CONSTANT_ENCAPSED_STRING
|
||||
%token T_ECHO
|
||||
%token T_DO
|
||||
%token T_WHILE
|
||||
%token T_ENDWHILE
|
||||
%token T_FOR
|
||||
%token T_ENDFOR
|
||||
%token T_FOREACH
|
||||
%token T_ENDFOREACH
|
||||
%token T_DECLARE
|
||||
%token T_ENDDECLARE
|
||||
%token T_AS
|
||||
%token T_SWITCH
|
||||
%token T_MATCH
|
||||
%token T_ENDSWITCH
|
||||
%token T_CASE
|
||||
%token T_DEFAULT
|
||||
%token T_BREAK
|
||||
%token T_CONTINUE
|
||||
%token T_GOTO
|
||||
%token T_FUNCTION
|
||||
%token T_FN
|
||||
%token T_CONST
|
||||
%token T_RETURN
|
||||
%token T_TRY
|
||||
%token T_CATCH
|
||||
%token T_FINALLY
|
||||
%token T_THROW
|
||||
%token T_USE
|
||||
%token T_INSTEADOF
|
||||
%token T_GLOBAL
|
||||
%right T_STATIC T_ABSTRACT T_FINAL T_PRIVATE T_PROTECTED T_PUBLIC T_READONLY
|
||||
%token T_VAR
|
||||
%token T_UNSET
|
||||
%token T_ISSET
|
||||
%token T_EMPTY
|
||||
%token T_HALT_COMPILER
|
||||
%token T_CLASS
|
||||
%token T_TRAIT
|
||||
%token T_INTERFACE
|
||||
%token T_ENUM
|
||||
%token T_EXTENDS
|
||||
%token T_IMPLEMENTS
|
||||
%token T_OBJECT_OPERATOR
|
||||
%token T_NULLSAFE_OBJECT_OPERATOR
|
||||
%token T_DOUBLE_ARROW
|
||||
%token T_LIST
|
||||
%token T_ARRAY
|
||||
%token T_CALLABLE
|
||||
%token T_CLASS_C
|
||||
%token T_TRAIT_C
|
||||
%token T_METHOD_C
|
||||
%token T_FUNC_C
|
||||
%token T_LINE
|
||||
%token T_FILE
|
||||
%token T_START_HEREDOC
|
||||
%token T_END_HEREDOC
|
||||
%token T_DOLLAR_OPEN_CURLY_BRACES
|
||||
%token T_CURLY_OPEN
|
||||
%token T_PAAMAYIM_NEKUDOTAYIM
|
||||
%token T_NAMESPACE
|
||||
%token T_NS_C
|
||||
%token T_DIR
|
||||
%token T_NS_SEPARATOR
|
||||
%token T_ELLIPSIS
|
||||
%token T_NAME_FULLY_QUALIFIED
|
||||
%token T_NAME_QUALIFIED
|
||||
%token T_NAME_RELATIVE
|
||||
%token T_ATTRIBUTE
|
||||
%token T_ENUM
|
||||
|
||||
%%
|
||||
|
||||
@ -117,7 +235,7 @@ top_statement:
|
||||
statement { $$ = $1; }
|
||||
| function_declaration_statement { $$ = $1; }
|
||||
| class_declaration_statement { $$ = $1; }
|
||||
| T_HALT_COMPILER
|
||||
| T_HALT_COMPILER '(' ')' ';'
|
||||
{ $$ = Stmt\HaltCompiler[$this->lexer->handleHaltCompiler()]; }
|
||||
| T_NAMESPACE namespace_declaration_name semi
|
||||
{ $$ = Stmt\Namespace_[$2, null];
|
||||
@ -180,16 +298,16 @@ non_empty_inline_use_declarations:
|
||||
|
||||
unprefixed_use_declaration:
|
||||
namespace_name
|
||||
{ $$ = Stmt\UseUse[$1, null, Stmt\Use_::TYPE_UNKNOWN]; $this->checkUseUse($$, #1); }
|
||||
{ $$ = Node\UseItem[$1, null, Stmt\Use_::TYPE_UNKNOWN]; $this->checkUseUse($$, #1); }
|
||||
| namespace_name T_AS identifier_not_reserved
|
||||
{ $$ = Stmt\UseUse[$1, $3, Stmt\Use_::TYPE_UNKNOWN]; $this->checkUseUse($$, #3); }
|
||||
{ $$ = Node\UseItem[$1, $3, Stmt\Use_::TYPE_UNKNOWN]; $this->checkUseUse($$, #3); }
|
||||
;
|
||||
|
||||
use_declaration:
|
||||
legacy_namespace_name
|
||||
{ $$ = Stmt\UseUse[$1, null, Stmt\Use_::TYPE_UNKNOWN]; $this->checkUseUse($$, #1); }
|
||||
{ $$ = Node\UseItem[$1, null, Stmt\Use_::TYPE_UNKNOWN]; $this->checkUseUse($$, #1); }
|
||||
| legacy_namespace_name T_AS identifier_not_reserved
|
||||
{ $$ = Stmt\UseUse[$1, $3, Stmt\Use_::TYPE_UNKNOWN]; $this->checkUseUse($$, #3); }
|
||||
{ $$ = Node\UseItem[$1, $3, Stmt\Use_::TYPE_UNKNOWN]; $this->checkUseUse($$, #3); }
|
||||
;
|
||||
|
||||
inline_use_declaration:
|
||||
@ -221,10 +339,7 @@ non_empty_class_const_list:
|
||||
;
|
||||
|
||||
class_const:
|
||||
T_STRING '=' expr
|
||||
{ $$ = Node\Const_[new Node\Identifier($1, stackAttributes(#1)), $3]; }
|
||||
| semi_reserved '=' expr
|
||||
{ $$ = Node\Const_[new Node\Identifier($1, stackAttributes(#1)), $3]; }
|
||||
identifier_maybe_reserved '=' expr { $$ = Node\Const_[$1, $3]; }
|
||||
;
|
||||
|
||||
inner_statement_list_ex:
|
||||
@ -353,23 +468,15 @@ block_or_error:
|
||||
| error { $$ = []; }
|
||||
;
|
||||
|
||||
identifier_maybe_readonly:
|
||||
identifier_not_reserved { $$ = $1; }
|
||||
| T_READONLY { $$ = Node\Identifier[$1]; }
|
||||
;
|
||||
|
||||
function_declaration_statement:
|
||||
T_FUNCTION optional_ref identifier_maybe_readonly '(' parameter_list ')' optional_return_type block_or_error
|
||||
T_FUNCTION optional_ref identifier_not_reserved '(' parameter_list ')' optional_return_type block_or_error
|
||||
{ $$ = Stmt\Function_[$3, ['byRef' => $2, 'params' => $5, 'returnType' => $7, 'stmts' => $8, 'attrGroups' => []]]; }
|
||||
| attributes T_FUNCTION optional_ref identifier_maybe_readonly '(' parameter_list ')' optional_return_type block_or_error
|
||||
| attributes T_FUNCTION optional_ref identifier_not_reserved '(' parameter_list ')' optional_return_type block_or_error
|
||||
{ $$ = Stmt\Function_[$4, ['byRef' => $3, 'params' => $6, 'returnType' => $8, 'stmts' => $9, 'attrGroups' => $1]]; }
|
||||
;
|
||||
|
||||
class_declaration_statement:
|
||||
class_entry_type identifier_not_reserved 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_not_reserved extends_from implements_list '{' class_statement_list '}'
|
||||
optional_attributes class_entry_type identifier_not_reserved extends_from implements_list '{' class_statement_list '}'
|
||||
{ $$ = Stmt\Class_[$3, ['type' => $2, 'extends' => $4, 'implements' => $5, 'stmts' => $7, 'attrGroups' => $1]];
|
||||
$this->checkClass($$, #3); }
|
||||
| optional_attributes T_INTERFACE identifier_not_reserved interface_extends_list '{' class_statement_list '}'
|
||||
@ -402,9 +509,9 @@ class_modifiers:
|
||||
;
|
||||
|
||||
class_modifier:
|
||||
T_ABSTRACT { $$ = Stmt\Class_::MODIFIER_ABSTRACT; }
|
||||
| T_FINAL { $$ = Stmt\Class_::MODIFIER_FINAL; }
|
||||
| T_READONLY { $$ = Stmt\Class_::MODIFIER_READONLY; }
|
||||
T_ABSTRACT { $$ = Modifiers::ABSTRACT; }
|
||||
| T_FINAL { $$ = Modifiers::FINAL; }
|
||||
| T_READONLY { $$ = Modifiers::READONLY; }
|
||||
;
|
||||
|
||||
extends_from:
|
||||
@ -457,7 +564,7 @@ non_empty_declare_list:
|
||||
;
|
||||
|
||||
declare_list_element:
|
||||
identifier_not_reserved '=' expr { $$ = Stmt\DeclareDeclare[$1, $3]; }
|
||||
identifier_not_reserved '=' expr { $$ = Node\DeclareItem[$1, $3]; }
|
||||
;
|
||||
|
||||
switch_case_list:
|
||||
@ -521,8 +628,7 @@ new_elseif_list:
|
||||
;
|
||||
|
||||
new_elseif:
|
||||
T_ELSEIF '(' expr ')' ':' inner_statement_list
|
||||
{ $$ = Stmt\ElseIf_[$3, $6]; $this->fixupAlternativeElse($$); }
|
||||
T_ELSEIF '(' expr ')' ':' inner_statement_list { $$ = Stmt\ElseIf_[$3, $6]; }
|
||||
;
|
||||
|
||||
else_single:
|
||||
@ -532,15 +638,15 @@ else_single:
|
||||
|
||||
new_else_single:
|
||||
/* empty */ { $$ = null; }
|
||||
| T_ELSE ':' inner_statement_list
|
||||
{ $$ = Stmt\Else_[$3]; $this->fixupAlternativeElse($$); }
|
||||
| T_ELSE ':' inner_statement_list { $$ = Stmt\Else_[$3]; }
|
||||
;
|
||||
|
||||
foreach_variable:
|
||||
variable { $$ = array($1, false); }
|
||||
| ampersand variable { $$ = array($2, true); }
|
||||
| list_expr { $$ = array($1, false); }
|
||||
| array_short_syntax { $$ = array($1, false); }
|
||||
| array_short_syntax
|
||||
{ $$ = array($this->fixupArrayDestructuring($1), false); }
|
||||
;
|
||||
|
||||
parameter_list:
|
||||
@ -560,10 +666,10 @@ optional_property_modifiers:
|
||||
;
|
||||
|
||||
property_modifier:
|
||||
T_PUBLIC { $$ = Stmt\Class_::MODIFIER_PUBLIC; }
|
||||
| T_PROTECTED { $$ = Stmt\Class_::MODIFIER_PROTECTED; }
|
||||
| T_PRIVATE { $$ = Stmt\Class_::MODIFIER_PRIVATE; }
|
||||
| T_READONLY { $$ = Stmt\Class_::MODIFIER_READONLY; }
|
||||
T_PUBLIC { $$ = Modifiers::PUBLIC; }
|
||||
| T_PROTECTED { $$ = Modifiers::PROTECTED; }
|
||||
| T_PRIVATE { $$ = Modifiers::PRIVATE; }
|
||||
| T_READONLY { $$ = Modifiers::READONLY; }
|
||||
;
|
||||
|
||||
parameter:
|
||||
@ -703,8 +809,8 @@ non_empty_static_var_list:
|
||||
;
|
||||
|
||||
static_var:
|
||||
plain_variable { $$ = Stmt\StaticVar[$1, null]; }
|
||||
| plain_variable '=' expr { $$ = Stmt\StaticVar[$1, $3]; }
|
||||
plain_variable { $$ = Node\StaticVar[$1, null]; }
|
||||
| plain_variable '=' expr { $$ = Node\StaticVar[$1, $3]; }
|
||||
;
|
||||
|
||||
class_statement_list_ex:
|
||||
@ -725,9 +831,6 @@ class_statement:
|
||||
| optional_attributes method_modifiers T_CONST class_const_list semi
|
||||
{ $$ = new Stmt\ClassConst($4, $2, attributes(), $1);
|
||||
$this->checkClassConst($$, #2); }
|
||||
| optional_attributes method_modifiers T_CONST type_expr class_const_list semi
|
||||
{ $$ = new Stmt\ClassConst($5, $2, attributes(), $1, $4);
|
||||
$this->checkClassConst($$, #2); }
|
||||
| optional_attributes method_modifiers T_FUNCTION optional_ref identifier_maybe_reserved '(' parameter_list ')'
|
||||
optional_return_type method_body
|
||||
{ $$ = Stmt\ClassMethod[$5, ['type' => $2, 'byRef' => $4, 'params' => $7, 'returnType' => $9, 'stmts' => $10, 'attrGroups' => $1]];
|
||||
@ -790,13 +893,13 @@ non_empty_member_modifiers:
|
||||
;
|
||||
|
||||
member_modifier:
|
||||
T_PUBLIC { $$ = Stmt\Class_::MODIFIER_PUBLIC; }
|
||||
| T_PROTECTED { $$ = Stmt\Class_::MODIFIER_PROTECTED; }
|
||||
| T_PRIVATE { $$ = Stmt\Class_::MODIFIER_PRIVATE; }
|
||||
| T_STATIC { $$ = Stmt\Class_::MODIFIER_STATIC; }
|
||||
| T_ABSTRACT { $$ = Stmt\Class_::MODIFIER_ABSTRACT; }
|
||||
| T_FINAL { $$ = Stmt\Class_::MODIFIER_FINAL; }
|
||||
| T_READONLY { $$ = Stmt\Class_::MODIFIER_READONLY; }
|
||||
T_PUBLIC { $$ = Modifiers::PUBLIC; }
|
||||
| T_PROTECTED { $$ = Modifiers::PROTECTED; }
|
||||
| T_PRIVATE { $$ = Modifiers::PRIVATE; }
|
||||
| T_STATIC { $$ = Modifiers::STATIC; }
|
||||
| T_ABSTRACT { $$ = Modifiers::ABSTRACT; }
|
||||
| T_FINAL { $$ = Modifiers::FINAL; }
|
||||
| T_READONLY { $$ = Modifiers::READONLY; }
|
||||
;
|
||||
|
||||
property_declaration_list:
|
||||
@ -814,8 +917,8 @@ property_decl_name:
|
||||
;
|
||||
|
||||
property_declaration:
|
||||
property_decl_name { $$ = Stmt\PropertyProperty[$1, null]; }
|
||||
| property_decl_name '=' expr { $$ = Stmt\PropertyProperty[$1, $3]; }
|
||||
property_decl_name { $$ = Node\PropertyItem[$1, null]; }
|
||||
| property_decl_name '=' expr { $$ = Node\PropertyItem[$1, $3]; }
|
||||
;
|
||||
|
||||
expr_list_forbid_comma:
|
||||
@ -839,9 +942,16 @@ for_expr:
|
||||
expr:
|
||||
variable { $$ = $1; }
|
||||
| list_expr '=' expr { $$ = Expr\Assign[$1, $3]; }
|
||||
| array_short_syntax '=' expr { $$ = Expr\Assign[$1, $3]; }
|
||||
| array_short_syntax '=' expr
|
||||
{ $$ = Expr\Assign[$this->fixupArrayDestructuring($1), $3]; }
|
||||
| variable '=' expr { $$ = Expr\Assign[$1, $3]; }
|
||||
| variable '=' ampersand variable { $$ = Expr\AssignRef[$1, $4]; }
|
||||
| variable '=' ampersand new_expr
|
||||
{ $$ = Expr\AssignRef[$1, $4];
|
||||
if (!$this->phpVersion->allowsAssignNewByReference()) {
|
||||
$this->emitError(new Error('Cannot assign new by reference', attributes()));
|
||||
}
|
||||
}
|
||||
| new_expr { $$ = $1; }
|
||||
| match { $$ = $1; }
|
||||
| T_CLONE expr { $$ = Expr\Clone_[$2]; }
|
||||
@ -949,8 +1059,8 @@ expr:
|
||||
;
|
||||
|
||||
anonymous_class:
|
||||
optional_attributes class_entry_type ctor_arguments extends_from implements_list '{' class_statement_list '}'
|
||||
{ $$ = array(Stmt\Class_[null, ['type' => $2, 'extends' => $4, 'implements' => $5, 'stmts' => $7, 'attrGroups' => $1]], $3);
|
||||
optional_attributes T_CLASS ctor_arguments extends_from implements_list '{' class_statement_list '}'
|
||||
{ $$ = array(Stmt\Class_[null, ['type' => 0, 'extends' => $4, 'implements' => $5, 'stmts' => $7, 'attrGroups' => $1]], $3);
|
||||
$this->checkClass($$[0], -1); }
|
||||
;
|
||||
|
||||
@ -978,13 +1088,8 @@ lexical_var:
|
||||
optional_ref plain_variable { $$ = Expr\ClosureUse[$2, $1]; }
|
||||
;
|
||||
|
||||
name_readonly:
|
||||
T_READONLY { $$ = Name[$1]; }
|
||||
;
|
||||
|
||||
function_call:
|
||||
name argument_list { $$ = Expr\FuncCall[$1, $2]; }
|
||||
| name_readonly argument_list { $$ = Expr\FuncCall[$1, $2]; }
|
||||
| callable_expr argument_list { $$ = Expr\FuncCall[$1, $2]; }
|
||||
| class_name_or_var T_PAAMAYIM_NEKUDOTAYIM member_name argument_list
|
||||
{ $$ = Expr\StaticCall[$1, $3, $4]; }
|
||||
@ -1022,7 +1127,7 @@ exit_expr:
|
||||
backticks_expr:
|
||||
/* empty */ { $$ = array(); }
|
||||
| T_ENCAPSED_AND_WHITESPACE
|
||||
{ $$ = array(Scalar\EncapsedStringPart[Scalar\String_::parseEscapeSequences($1, '`')]); }
|
||||
{ $$ = array(Node\InterpolatedStringPart[Scalar\String_::parseEscapeSequences($1, '`')]); }
|
||||
| encaps_list { parseEncapsed($1, '`', true); $$ = $1; }
|
||||
;
|
||||
|
||||
@ -1046,8 +1151,6 @@ constant:
|
||||
class_constant:
|
||||
class_name_or_var T_PAAMAYIM_NEKUDOTAYIM identifier_maybe_reserved
|
||||
{ $$ = Expr\ClassConstFetch[$1, $3]; }
|
||||
| class_name_or_var T_PAAMAYIM_NEKUDOTAYIM '{' expr '}'
|
||||
{ $$ = Expr\ClassConstFetch[$1, $4]; }
|
||||
/* We interpret an isolated FOO:: as an unfinished class constant fetch. It could also be
|
||||
an unfinished static property fetch or unfinished scoped call. */
|
||||
| class_name_or_var T_PAAMAYIM_NEKUDOTAYIM error
|
||||
@ -1063,17 +1166,19 @@ array_short_syntax:
|
||||
dereferencable_scalar:
|
||||
T_ARRAY '(' array_pair_list ')'
|
||||
{ $attrs = attributes(); $attrs['kind'] = Expr\Array_::KIND_LONG;
|
||||
$$ = new Expr\Array_($3, $attrs); }
|
||||
| array_short_syntax { $$ = $1; }
|
||||
$$ = new Expr\Array_($3, $attrs);
|
||||
$this->createdArrays->attach($$); }
|
||||
| array_short_syntax { $$ = $1; $this->createdArrays->attach($$); }
|
||||
| T_CONSTANT_ENCAPSED_STRING { $$ = Scalar\String_::fromString($1, attributes()); }
|
||||
| '"' encaps_list '"'
|
||||
{ $attrs = attributes(); $attrs['kind'] = Scalar\String_::KIND_DOUBLE_QUOTED;
|
||||
parseEncapsed($2, '"', true); $$ = new Scalar\Encapsed($2, $attrs); }
|
||||
parseEncapsed($2, '"', true); $$ = new Scalar\InterpolatedString($2, $attrs); }
|
||||
;
|
||||
|
||||
scalar:
|
||||
T_LNUMBER { $$ = $this->parseLNumber($1, attributes()); }
|
||||
| T_DNUMBER { $$ = Scalar\DNumber::fromString($1, attributes()); }
|
||||
T_LNUMBER
|
||||
{ $$ = $this->parseLNumber($1, attributes(), $this->phpVersion->allowsInvalidOctals()); }
|
||||
| T_DNUMBER { $$ = Scalar\Float_::fromString($1, attributes()); }
|
||||
| dereferencable_scalar { $$ = $1; }
|
||||
| constant { $$ = $1; }
|
||||
| class_constant { $$ = $1; }
|
||||
@ -1176,12 +1281,14 @@ property_name:
|
||||
;
|
||||
|
||||
list_expr:
|
||||
T_LIST '(' inner_array_pair_list ')' { $$ = Expr\List_[$3]; }
|
||||
T_LIST '(' inner_array_pair_list ')'
|
||||
{ $$ = Expr\List_[$3]; $$->setAttribute('kind', Expr\List_::KIND_LIST);
|
||||
$this->postprocessList($$); }
|
||||
;
|
||||
|
||||
array_pair_list:
|
||||
inner_array_pair_list
|
||||
{ $$ = $1; $end = count($$)-1; if ($$[$end] === null) array_pop($$); }
|
||||
{ $$ = $1; $end = count($$)-1; if ($$[$end]->value instanceof Expr\Error) array_pop($$); }
|
||||
;
|
||||
|
||||
comma_or_error:
|
||||
@ -1196,14 +1303,18 @@ inner_array_pair_list:
|
||||
;
|
||||
|
||||
array_pair:
|
||||
expr { $$ = Expr\ArrayItem[$1, null, false]; }
|
||||
| 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 ampersand variable { $$ = Expr\ArrayItem[$4, $1, true]; }
|
||||
| expr T_DOUBLE_ARROW list_expr { $$ = Expr\ArrayItem[$3, $1, false]; }
|
||||
| T_ELLIPSIS expr { $$ = new Expr\ArrayItem($2, null, false, attributes(), true); }
|
||||
| /* empty */ { $$ = null; }
|
||||
expr { $$ = Node\ArrayItem[$1, null, false]; }
|
||||
| ampersand variable { $$ = Node\ArrayItem[$2, null, true]; }
|
||||
| list_expr { $$ = Node\ArrayItem[$1, null, false]; }
|
||||
| expr T_DOUBLE_ARROW expr { $$ = Node\ArrayItem[$3, $1, false]; }
|
||||
| expr T_DOUBLE_ARROW ampersand variable { $$ = Node\ArrayItem[$4, $1, true]; }
|
||||
| expr T_DOUBLE_ARROW list_expr { $$ = Node\ArrayItem[$3, $1, false]; }
|
||||
| T_ELLIPSIS expr { $$ = Node\ArrayItem[$2, null, false, attributes(), true]; }
|
||||
| /* empty */
|
||||
{ /* Create an Error node now to remember the position. We'll later either report an error,
|
||||
or convert this into a null element, depending on whether this is a creation or destructuring context. */
|
||||
$attrs = $this->createEmptyElemAttributes($this->lookaheadStartAttributes);
|
||||
$$ = new Node\ArrayItem(new Expr\Error($attrs), null, false, $attrs); }
|
||||
;
|
||||
|
||||
encaps_list:
|
||||
@ -1214,7 +1325,7 @@ encaps_list:
|
||||
;
|
||||
|
||||
encaps_string_part:
|
||||
T_ENCAPSED_AND_WHITESPACE { $$ = Scalar\EncapsedStringPart[$1]; }
|
||||
T_ENCAPSED_AND_WHITESPACE { $$ = Node\InterpolatedStringPart[$1]; }
|
||||
;
|
||||
|
||||
encaps_str_varname:
|
1046
grammar/php5.y
1046
grammar/php5.y
File diff suppressed because it is too large
Load Diff
@ -1,4 +1,4 @@
|
||||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
///////////////////////////////
|
||||
/// Utility regex constants ///
|
||||
@ -30,7 +30,7 @@ function preprocessGrammar($code) {
|
||||
function resolveNodes($code) {
|
||||
return preg_replace_callback(
|
||||
'~\b(?<name>[A-Z][a-zA-Z_\\\\]++)\s*' . PARAMS . '~',
|
||||
function($matches) {
|
||||
function ($matches) {
|
||||
// recurse
|
||||
$matches['params'] = resolveNodes($matches['params']);
|
||||
|
||||
@ -53,7 +53,7 @@ function resolveNodes($code) {
|
||||
function resolveMacros($code) {
|
||||
return preg_replace_callback(
|
||||
'~\b(?<!::|->)(?!array\()(?<name>[a-z][A-Za-z]++)' . ARGS . '~',
|
||||
function($matches) {
|
||||
function ($matches) {
|
||||
// recurse
|
||||
$matches['args'] = resolveMacros($matches['args']);
|
||||
|
||||
@ -106,7 +106,7 @@ function resolveMacros($code) {
|
||||
if ('parseEncapsed' === $name) {
|
||||
assertArgs(3, $args, $name);
|
||||
|
||||
return 'foreach (' . $args[0] . ' as $s) { if ($s instanceof Node\Scalar\EncapsedStringPart) {'
|
||||
return 'foreach (' . $args[0] . ' as $s) { if ($s instanceof Node\InterpolatedStringPart) {'
|
||||
. ' $s->value = Node\Scalar\String_::parseEscapeSequences($s->value, ' . $args[1] . ', ' . $args[2] . '); } }';
|
||||
}
|
||||
|
||||
|
@ -1,19 +1,17 @@
|
||||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
require __DIR__ . '/phpyLang.php';
|
||||
|
||||
$grammarFileToName = [
|
||||
__DIR__ . '/php5.y' => 'Php5',
|
||||
__DIR__ . '/php7.y' => 'Php7',
|
||||
$parserToDefines = [
|
||||
'Php7' => ['PHP7' => true],
|
||||
'Php8' => ['PHP8' => true],
|
||||
];
|
||||
|
||||
$tokensFile = __DIR__ . '/tokens.y';
|
||||
$tokensTemplate = __DIR__ . '/tokens.template';
|
||||
$grammarFile = __DIR__ . '/php.y';
|
||||
$skeletonFile = __DIR__ . '/parser.template';
|
||||
$tmpGrammarFile = __DIR__ . '/tmp_parser.phpy';
|
||||
$tmpResultFile = __DIR__ . '/tmp_parser.php';
|
||||
$resultDir = __DIR__ . '/../lib/PhpParser/Parser';
|
||||
$tokensResultsFile = $resultDir . '/Tokens.php';
|
||||
|
||||
$kmyacc = getenv('KMYACC');
|
||||
if (!$kmyacc) {
|
||||
@ -29,13 +27,11 @@ $optionKeepTmpGrammar = isset($options['--keep-tmp-grammar']);
|
||||
/// Main script ///
|
||||
///////////////////
|
||||
|
||||
$tokens = file_get_contents($tokensFile);
|
||||
|
||||
foreach ($grammarFileToName as $grammarFile => $name) {
|
||||
foreach ($parserToDefines as $name => $defines) {
|
||||
echo "Building temporary $name grammar file.\n";
|
||||
|
||||
$grammarCode = file_get_contents($grammarFile);
|
||||
$grammarCode = str_replace('%tokens', $tokens, $grammarCode);
|
||||
$grammarCode = replaceIfBlocks($grammarCode, $defines);
|
||||
$grammarCode = preprocessGrammar($grammarCode);
|
||||
|
||||
file_put_contents($tmpGrammarFile, $grammarCode);
|
||||
@ -52,10 +48,6 @@ foreach ($grammarFileToName as $grammarFile => $name) {
|
||||
file_put_contents("$resultDir/$name.php", $resultCode);
|
||||
unlink($tmpResultFile);
|
||||
|
||||
echo "Building token definition.\n";
|
||||
$output = execCmd("$kmyacc -m $tokensTemplate $tmpGrammarFile");
|
||||
rename($tmpResultFile, $tokensResultsFile);
|
||||
|
||||
if (!$optionKeepTmpGrammar) {
|
||||
unlink($tmpGrammarFile);
|
||||
}
|
||||
@ -72,10 +64,17 @@ function ensureDirExists($dir) {
|
||||
}
|
||||
|
||||
function execCmd($cmd) {
|
||||
$output = trim(shell_exec("$cmd 2>&1"));
|
||||
$output = trim(shell_exec("$cmd 2>&1") ?? '');
|
||||
if ($output !== "") {
|
||||
echo "> " . $cmd . "\n";
|
||||
echo $output;
|
||||
}
|
||||
return $output;
|
||||
}
|
||||
|
||||
function replaceIfBlocks(string $code, array $defines): string {
|
||||
return preg_replace_callback('/\n#if\s+(\w+)\n(.*?)\n#endif/s', function ($matches) use ($defines) {
|
||||
$value = $defines[$matches[1]] ?? false;
|
||||
return $value ? $matches[2] : '';
|
||||
}, $code);
|
||||
}
|
||||
|
@ -1,17 +0,0 @@
|
||||
<?php
|
||||
$meta #
|
||||
#semval($) $this->semValue
|
||||
#semval($,%t) $this->semValue
|
||||
#semval(%n) $this->stackPos-(%l-%n)
|
||||
#semval(%n,%t) $this->stackPos-(%l-%n)
|
||||
|
||||
namespace PhpParser\Parser;
|
||||
#include;
|
||||
|
||||
/* GENERATED file based on grammar/tokens.y */
|
||||
final class Tokens
|
||||
{
|
||||
#tokenval
|
||||
const %s = %n;
|
||||
#endtokenval
|
||||
}
|
115
grammar/tokens.y
115
grammar/tokens.y
@ -1,115 +0,0 @@
|
||||
/* We currently rely on the token ID mapping to be the same between PHP 5 and PHP 7 - so the same lexer can be used for
|
||||
* both. This is enforced by sharing this token file. */
|
||||
|
||||
%right T_THROW
|
||||
%left T_INCLUDE T_INCLUDE_ONCE T_EVAL T_REQUIRE T_REQUIRE_ONCE
|
||||
%left ','
|
||||
%left T_LOGICAL_OR
|
||||
%left T_LOGICAL_XOR
|
||||
%left T_LOGICAL_AND
|
||||
%right T_PRINT
|
||||
%right T_YIELD
|
||||
%right T_DOUBLE_ARROW
|
||||
%right T_YIELD_FROM
|
||||
%left '=' T_PLUS_EQUAL T_MINUS_EQUAL T_MUL_EQUAL T_DIV_EQUAL T_CONCAT_EQUAL T_MOD_EQUAL T_AND_EQUAL T_OR_EQUAL T_XOR_EQUAL T_SL_EQUAL T_SR_EQUAL T_POW_EQUAL T_COALESCE_EQUAL
|
||||
%left '?' ':'
|
||||
%right T_COALESCE
|
||||
%left T_BOOLEAN_OR
|
||||
%left T_BOOLEAN_AND
|
||||
%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
|
||||
%left '+' '-' '.'
|
||||
%left '*' '/' '%'
|
||||
%right '!'
|
||||
%nonassoc T_INSTANCEOF
|
||||
%right '~' T_INC T_DEC T_INT_CAST T_DOUBLE_CAST T_STRING_CAST T_ARRAY_CAST T_OBJECT_CAST T_BOOL_CAST T_UNSET_CAST '@'
|
||||
%right T_POW
|
||||
%right '['
|
||||
%nonassoc T_NEW T_CLONE
|
||||
%token T_EXIT
|
||||
%token T_IF
|
||||
%left T_ELSEIF
|
||||
%left T_ELSE
|
||||
%left T_ENDIF
|
||||
%token T_LNUMBER
|
||||
%token T_DNUMBER
|
||||
%token T_STRING
|
||||
%token T_STRING_VARNAME
|
||||
%token T_VARIABLE
|
||||
%token T_NUM_STRING
|
||||
%token T_INLINE_HTML
|
||||
%token T_ENCAPSED_AND_WHITESPACE
|
||||
%token T_CONSTANT_ENCAPSED_STRING
|
||||
%token T_ECHO
|
||||
%token T_DO
|
||||
%token T_WHILE
|
||||
%token T_ENDWHILE
|
||||
%token T_FOR
|
||||
%token T_ENDFOR
|
||||
%token T_FOREACH
|
||||
%token T_ENDFOREACH
|
||||
%token T_DECLARE
|
||||
%token T_ENDDECLARE
|
||||
%token T_AS
|
||||
%token T_SWITCH
|
||||
%token T_MATCH
|
||||
%token T_ENDSWITCH
|
||||
%token T_CASE
|
||||
%token T_DEFAULT
|
||||
%token T_BREAK
|
||||
%token T_CONTINUE
|
||||
%token T_GOTO
|
||||
%token T_FUNCTION
|
||||
%token T_FN
|
||||
%token T_CONST
|
||||
%token T_RETURN
|
||||
%token T_TRY
|
||||
%token T_CATCH
|
||||
%token T_FINALLY
|
||||
%token T_THROW
|
||||
%token T_USE
|
||||
%token T_INSTEADOF
|
||||
%token T_GLOBAL
|
||||
%right T_STATIC T_ABSTRACT T_FINAL T_PRIVATE T_PROTECTED T_PUBLIC T_READONLY
|
||||
%token T_VAR
|
||||
%token T_UNSET
|
||||
%token T_ISSET
|
||||
%token T_EMPTY
|
||||
%token T_HALT_COMPILER
|
||||
%token T_CLASS
|
||||
%token T_TRAIT
|
||||
%token T_INTERFACE
|
||||
%token T_ENUM
|
||||
%token T_EXTENDS
|
||||
%token T_IMPLEMENTS
|
||||
%token T_OBJECT_OPERATOR
|
||||
%token T_NULLSAFE_OBJECT_OPERATOR
|
||||
%token T_DOUBLE_ARROW
|
||||
%token T_LIST
|
||||
%token T_ARRAY
|
||||
%token T_CALLABLE
|
||||
%token T_CLASS_C
|
||||
%token T_TRAIT_C
|
||||
%token T_METHOD_C
|
||||
%token T_FUNC_C
|
||||
%token T_LINE
|
||||
%token T_FILE
|
||||
%token T_START_HEREDOC
|
||||
%token T_END_HEREDOC
|
||||
%token T_DOLLAR_OPEN_CURLY_BRACES
|
||||
%token T_CURLY_OPEN
|
||||
%token T_PAAMAYIM_NEKUDOTAYIM
|
||||
%token T_NAMESPACE
|
||||
%token T_NS_C
|
||||
%token T_DIR
|
||||
%token T_NS_SEPARATOR
|
||||
%token T_ELLIPSIS
|
||||
%token T_NAME_FULLY_QUALIFIED
|
||||
%token T_NAME_QUALIFIED
|
||||
%token T_NAME_RELATIVE
|
||||
%token T_ATTRIBUTE
|
||||
%token T_ENUM
|
@ -2,12 +2,11 @@
|
||||
|
||||
namespace PhpParser;
|
||||
|
||||
interface Builder
|
||||
{
|
||||
interface Builder {
|
||||
/**
|
||||
* Returns the built node.
|
||||
*
|
||||
* @return Node The built node
|
||||
*/
|
||||
public function getNode() : Node;
|
||||
public function getNode(): Node;
|
||||
}
|
||||
|
@ -6,21 +6,19 @@ namespace PhpParser\Builder;
|
||||
|
||||
use PhpParser;
|
||||
use PhpParser\BuilderHelpers;
|
||||
use PhpParser\Modifiers;
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Const_;
|
||||
use PhpParser\Node\Identifier;
|
||||
use PhpParser\Node\Stmt;
|
||||
|
||||
class ClassConst implements PhpParser\Builder
|
||||
{
|
||||
class ClassConst implements PhpParser\Builder {
|
||||
protected $flags = 0;
|
||||
protected $attributes = [];
|
||||
protected $constants = [];
|
||||
|
||||
/** @var Node\AttributeGroup[] */
|
||||
protected $attributeGroups = [];
|
||||
/** @var Identifier|Node\Name|Node\ComplexType */
|
||||
protected $type;
|
||||
|
||||
/**
|
||||
* Creates a class constant builder
|
||||
@ -52,7 +50,7 @@ class ClassConst implements PhpParser\Builder
|
||||
* @return $this The builder instance (for fluid interface)
|
||||
*/
|
||||
public function makePublic() {
|
||||
$this->flags = BuilderHelpers::addModifier($this->flags, Stmt\Class_::MODIFIER_PUBLIC);
|
||||
$this->flags = BuilderHelpers::addModifier($this->flags, Modifiers::PUBLIC);
|
||||
|
||||
return $this;
|
||||
}
|
||||
@ -63,7 +61,7 @@ class ClassConst implements PhpParser\Builder
|
||||
* @return $this The builder instance (for fluid interface)
|
||||
*/
|
||||
public function makeProtected() {
|
||||
$this->flags = BuilderHelpers::addModifier($this->flags, Stmt\Class_::MODIFIER_PROTECTED);
|
||||
$this->flags = BuilderHelpers::addModifier($this->flags, Modifiers::PROTECTED);
|
||||
|
||||
return $this;
|
||||
}
|
||||
@ -74,7 +72,7 @@ class ClassConst implements PhpParser\Builder
|
||||
* @return $this The builder instance (for fluid interface)
|
||||
*/
|
||||
public function makePrivate() {
|
||||
$this->flags = BuilderHelpers::addModifier($this->flags, Stmt\Class_::MODIFIER_PRIVATE);
|
||||
$this->flags = BuilderHelpers::addModifier($this->flags, Modifiers::PRIVATE);
|
||||
|
||||
return $this;
|
||||
}
|
||||
@ -85,7 +83,7 @@ class ClassConst implements PhpParser\Builder
|
||||
* @return $this The builder instance (for fluid interface)
|
||||
*/
|
||||
public function makeFinal() {
|
||||
$this->flags = BuilderHelpers::addModifier($this->flags, Stmt\Class_::MODIFIER_FINAL);
|
||||
$this->flags = BuilderHelpers::addModifier($this->flags, Modifiers::FINAL);
|
||||
|
||||
return $this;
|
||||
}
|
||||
@ -118,19 +116,6 @@ class ClassConst implements PhpParser\Builder
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the constant type.
|
||||
*
|
||||
* @param string|Node\Name|Identifier|Node\ComplexType $type
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setType($type) {
|
||||
$this->type = BuilderHelpers::normalizeType($type);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the built class node.
|
||||
*
|
||||
@ -141,8 +126,7 @@ class ClassConst implements PhpParser\Builder
|
||||
$this->constants,
|
||||
$this->flags,
|
||||
$this->attributes,
|
||||
$this->attributeGroups,
|
||||
$this->type
|
||||
$this->attributeGroups
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -4,12 +4,12 @@ namespace PhpParser\Builder;
|
||||
|
||||
use PhpParser;
|
||||
use PhpParser\BuilderHelpers;
|
||||
use PhpParser\Modifiers;
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Name;
|
||||
use PhpParser\Node\Stmt;
|
||||
|
||||
class Class_ extends Declaration
|
||||
{
|
||||
class Class_ extends Declaration {
|
||||
protected $name;
|
||||
|
||||
protected $extends = null;
|
||||
@ -67,7 +67,7 @@ class Class_ extends Declaration
|
||||
* @return $this The builder instance (for fluid interface)
|
||||
*/
|
||||
public function makeAbstract() {
|
||||
$this->flags = BuilderHelpers::addClassModifier($this->flags, Stmt\Class_::MODIFIER_ABSTRACT);
|
||||
$this->flags = BuilderHelpers::addClassModifier($this->flags, Modifiers::ABSTRACT);
|
||||
|
||||
return $this;
|
||||
}
|
||||
@ -78,13 +78,13 @@ class Class_ extends Declaration
|
||||
* @return $this The builder instance (for fluid interface)
|
||||
*/
|
||||
public function makeFinal() {
|
||||
$this->flags = BuilderHelpers::addClassModifier($this->flags, Stmt\Class_::MODIFIER_FINAL);
|
||||
$this->flags = BuilderHelpers::addClassModifier($this->flags, Modifiers::FINAL);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function makeReadonly() {
|
||||
$this->flags = BuilderHelpers::addClassModifier($this->flags, Stmt\Class_::MODIFIER_READONLY);
|
||||
$this->flags = BuilderHelpers::addClassModifier($this->flags, Modifiers::READONLY);
|
||||
|
||||
return $this;
|
||||
}
|
||||
@ -134,7 +134,7 @@ class Class_ extends Declaration
|
||||
*
|
||||
* @return Stmt\Class_ The built class node
|
||||
*/
|
||||
public function getNode() : PhpParser\Node {
|
||||
public function getNode(): PhpParser\Node {
|
||||
return new Stmt\Class_($this->name, [
|
||||
'flags' => $this->flags,
|
||||
'extends' => $this->extends,
|
||||
|
@ -5,8 +5,7 @@ namespace PhpParser\Builder;
|
||||
use PhpParser;
|
||||
use PhpParser\BuilderHelpers;
|
||||
|
||||
abstract class Declaration implements PhpParser\Builder
|
||||
{
|
||||
abstract class Declaration implements PhpParser\Builder {
|
||||
protected $attributes = [];
|
||||
|
||||
abstract public function addStmt($stmt);
|
||||
|
@ -10,8 +10,7 @@ use PhpParser\Node;
|
||||
use PhpParser\Node\Identifier;
|
||||
use PhpParser\Node\Stmt;
|
||||
|
||||
class EnumCase implements PhpParser\Builder
|
||||
{
|
||||
class EnumCase implements PhpParser\Builder {
|
||||
protected $name;
|
||||
protected $value = null;
|
||||
protected $attributes = [];
|
||||
@ -78,8 +77,8 @@ class EnumCase implements PhpParser\Builder
|
||||
return new Stmt\EnumCase(
|
||||
$this->name,
|
||||
$this->value,
|
||||
$this->attributeGroups,
|
||||
$this->attributes
|
||||
$this->attributes,
|
||||
$this->attributeGroups
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -9,8 +9,7 @@ use PhpParser\Node\Identifier;
|
||||
use PhpParser\Node\Name;
|
||||
use PhpParser\Node\Stmt;
|
||||
|
||||
class Enum_ extends Declaration
|
||||
{
|
||||
class Enum_ extends Declaration {
|
||||
protected $name;
|
||||
protected $scalarType = null;
|
||||
|
||||
@ -106,7 +105,7 @@ class Enum_ extends Declaration
|
||||
*
|
||||
* @return Stmt\Enum_ The built enum node
|
||||
*/
|
||||
public function getNode() : PhpParser\Node {
|
||||
public function getNode(): PhpParser\Node {
|
||||
return new Stmt\Enum_($this->name, [
|
||||
'scalarType' => $this->scalarType,
|
||||
'implements' => $this->implements,
|
||||
|
@ -5,8 +5,7 @@ namespace PhpParser\Builder;
|
||||
use PhpParser\BuilderHelpers;
|
||||
use PhpParser\Node;
|
||||
|
||||
abstract class FunctionLike extends Declaration
|
||||
{
|
||||
abstract class FunctionLike extends Declaration {
|
||||
protected $returnByRef = false;
|
||||
protected $params = [];
|
||||
|
||||
|
@ -7,8 +7,7 @@ use PhpParser\BuilderHelpers;
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Stmt;
|
||||
|
||||
class Function_ extends FunctionLike
|
||||
{
|
||||
class Function_ extends FunctionLike {
|
||||
protected $name;
|
||||
protected $stmts = [];
|
||||
|
||||
@ -55,7 +54,7 @@ class Function_ extends FunctionLike
|
||||
*
|
||||
* @return Stmt\Function_ The built function node
|
||||
*/
|
||||
public function getNode() : Node {
|
||||
public function getNode(): Node {
|
||||
return new Stmt\Function_($this->name, [
|
||||
'byRef' => $this->returnByRef,
|
||||
'params' => $this->params,
|
||||
|
@ -8,8 +8,7 @@ use PhpParser\Node;
|
||||
use PhpParser\Node\Name;
|
||||
use PhpParser\Node\Stmt;
|
||||
|
||||
class Interface_ extends Declaration
|
||||
{
|
||||
class Interface_ extends Declaration {
|
||||
protected $name;
|
||||
protected $extends = [];
|
||||
protected $constants = [];
|
||||
@ -83,7 +82,7 @@ class Interface_ extends Declaration
|
||||
*
|
||||
* @return Stmt\Interface_ The built interface node
|
||||
*/
|
||||
public function getNode() : PhpParser\Node {
|
||||
public function getNode(): PhpParser\Node {
|
||||
return new Stmt\Interface_($this->name, [
|
||||
'extends' => $this->extends,
|
||||
'stmts' => array_merge($this->constants, $this->methods),
|
||||
|
@ -4,11 +4,11 @@ namespace PhpParser\Builder;
|
||||
|
||||
use PhpParser;
|
||||
use PhpParser\BuilderHelpers;
|
||||
use PhpParser\Modifiers;
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Stmt;
|
||||
|
||||
class Method extends FunctionLike
|
||||
{
|
||||
class Method extends FunctionLike {
|
||||
protected $name;
|
||||
protected $flags = 0;
|
||||
|
||||
@ -33,7 +33,7 @@ class Method extends FunctionLike
|
||||
* @return $this The builder instance (for fluid interface)
|
||||
*/
|
||||
public function makePublic() {
|
||||
$this->flags = BuilderHelpers::addModifier($this->flags, Stmt\Class_::MODIFIER_PUBLIC);
|
||||
$this->flags = BuilderHelpers::addModifier($this->flags, Modifiers::PUBLIC);
|
||||
|
||||
return $this;
|
||||
}
|
||||
@ -44,7 +44,7 @@ class Method extends FunctionLike
|
||||
* @return $this The builder instance (for fluid interface)
|
||||
*/
|
||||
public function makeProtected() {
|
||||
$this->flags = BuilderHelpers::addModifier($this->flags, Stmt\Class_::MODIFIER_PROTECTED);
|
||||
$this->flags = BuilderHelpers::addModifier($this->flags, Modifiers::PROTECTED);
|
||||
|
||||
return $this;
|
||||
}
|
||||
@ -55,7 +55,7 @@ class Method extends FunctionLike
|
||||
* @return $this The builder instance (for fluid interface)
|
||||
*/
|
||||
public function makePrivate() {
|
||||
$this->flags = BuilderHelpers::addModifier($this->flags, Stmt\Class_::MODIFIER_PRIVATE);
|
||||
$this->flags = BuilderHelpers::addModifier($this->flags, Modifiers::PRIVATE);
|
||||
|
||||
return $this;
|
||||
}
|
||||
@ -66,7 +66,7 @@ class Method extends FunctionLike
|
||||
* @return $this The builder instance (for fluid interface)
|
||||
*/
|
||||
public function makeStatic() {
|
||||
$this->flags = BuilderHelpers::addModifier($this->flags, Stmt\Class_::MODIFIER_STATIC);
|
||||
$this->flags = BuilderHelpers::addModifier($this->flags, Modifiers::STATIC);
|
||||
|
||||
return $this;
|
||||
}
|
||||
@ -81,7 +81,7 @@ class Method extends FunctionLike
|
||||
throw new \LogicException('Cannot make method with statements abstract');
|
||||
}
|
||||
|
||||
$this->flags = BuilderHelpers::addModifier($this->flags, Stmt\Class_::MODIFIER_ABSTRACT);
|
||||
$this->flags = BuilderHelpers::addModifier($this->flags, Modifiers::ABSTRACT);
|
||||
$this->stmts = null; // abstract methods don't have statements
|
||||
|
||||
return $this;
|
||||
@ -93,7 +93,7 @@ class Method extends FunctionLike
|
||||
* @return $this The builder instance (for fluid interface)
|
||||
*/
|
||||
public function makeFinal() {
|
||||
$this->flags = BuilderHelpers::addModifier($this->flags, Stmt\Class_::MODIFIER_FINAL);
|
||||
$this->flags = BuilderHelpers::addModifier($this->flags, Modifiers::FINAL);
|
||||
|
||||
return $this;
|
||||
}
|
||||
@ -133,7 +133,7 @@ class Method extends FunctionLike
|
||||
*
|
||||
* @return Stmt\ClassMethod The built method node
|
||||
*/
|
||||
public function getNode() : Node {
|
||||
public function getNode(): Node {
|
||||
return new Stmt\ClassMethod($this->name, [
|
||||
'flags' => $this->flags,
|
||||
'byRef' => $this->returnByRef,
|
||||
|
@ -7,8 +7,7 @@ use PhpParser\BuilderHelpers;
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Stmt;
|
||||
|
||||
class Namespace_ extends Declaration
|
||||
{
|
||||
class Namespace_ extends Declaration {
|
||||
private $name;
|
||||
private $stmts = [];
|
||||
|
||||
@ -39,7 +38,7 @@ class Namespace_ extends Declaration
|
||||
*
|
||||
* @return Stmt\Namespace_ The built node
|
||||
*/
|
||||
public function getNode() : Node {
|
||||
public function getNode(): Node {
|
||||
return new Stmt\Namespace_($this->name, $this->stmts, $this->attributes);
|
||||
}
|
||||
}
|
||||
|
@ -6,8 +6,7 @@ use PhpParser;
|
||||
use PhpParser\BuilderHelpers;
|
||||
use PhpParser\Node;
|
||||
|
||||
class Param implements PhpParser\Builder
|
||||
{
|
||||
class Param implements PhpParser\Builder {
|
||||
protected $name;
|
||||
|
||||
protected $default = null;
|
||||
@ -19,8 +18,6 @@ class Param implements PhpParser\Builder
|
||||
|
||||
protected $variadic = false;
|
||||
|
||||
protected $flags = 0;
|
||||
|
||||
/** @var Node\AttributeGroup[] */
|
||||
protected $attributeGroups = [];
|
||||
|
||||
@ -62,19 +59,6 @@ class Param implements PhpParser\Builder
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets type for the parameter.
|
||||
*
|
||||
* @param string|Node\Name|Node\Identifier|Node\ComplexType $type Parameter type
|
||||
*
|
||||
* @return $this The builder instance (for fluid interface)
|
||||
*
|
||||
* @deprecated Use setType() instead
|
||||
*/
|
||||
public function setTypeHint($type) {
|
||||
return $this->setType($type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Make the parameter accept the value by reference.
|
||||
*
|
||||
@ -97,50 +81,6 @@ class Param implements PhpParser\Builder
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes the (promoted) parameter public.
|
||||
*
|
||||
* @return $this The builder instance (for fluid interface)
|
||||
*/
|
||||
public function makePublic() {
|
||||
$this->flags = BuilderHelpers::addModifier($this->flags, Node\Stmt\Class_::MODIFIER_PUBLIC);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes the (promoted) parameter protected.
|
||||
*
|
||||
* @return $this The builder instance (for fluid interface)
|
||||
*/
|
||||
public function makeProtected() {
|
||||
$this->flags = BuilderHelpers::addModifier($this->flags, Node\Stmt\Class_::MODIFIER_PROTECTED);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes the (promoted) parameter private.
|
||||
*
|
||||
* @return $this The builder instance (for fluid interface)
|
||||
*/
|
||||
public function makePrivate() {
|
||||
$this->flags = BuilderHelpers::addModifier($this->flags, Node\Stmt\Class_::MODIFIER_PRIVATE);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes the (promoted) parameter readonly.
|
||||
*
|
||||
* @return $this The builder instance (for fluid interface)
|
||||
*/
|
||||
public function makeReadonly() {
|
||||
$this->flags = BuilderHelpers::addModifier($this->flags, Node\Stmt\Class_::MODIFIER_READONLY);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an attribute group.
|
||||
*
|
||||
@ -159,10 +99,10 @@ class Param implements PhpParser\Builder
|
||||
*
|
||||
* @return Node\Param The built parameter node
|
||||
*/
|
||||
public function getNode() : Node {
|
||||
public function getNode(): Node {
|
||||
return new Node\Param(
|
||||
new Node\Expr\Variable($this->name),
|
||||
$this->default, $this->type, $this->byRef, $this->variadic, [], $this->flags, $this->attributeGroups
|
||||
$this->default, $this->type, $this->byRef, $this->variadic, [], 0, $this->attributeGroups
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -4,14 +4,14 @@ namespace PhpParser\Builder;
|
||||
|
||||
use PhpParser;
|
||||
use PhpParser\BuilderHelpers;
|
||||
use PhpParser\Modifiers;
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Identifier;
|
||||
use PhpParser\Node\Name;
|
||||
use PhpParser\Node\Stmt;
|
||||
use PhpParser\Node\ComplexType;
|
||||
|
||||
class Property implements PhpParser\Builder
|
||||
{
|
||||
class Property implements PhpParser\Builder {
|
||||
protected $name;
|
||||
|
||||
protected $flags = 0;
|
||||
@ -39,7 +39,7 @@ class Property implements PhpParser\Builder
|
||||
* @return $this The builder instance (for fluid interface)
|
||||
*/
|
||||
public function makePublic() {
|
||||
$this->flags = BuilderHelpers::addModifier($this->flags, Stmt\Class_::MODIFIER_PUBLIC);
|
||||
$this->flags = BuilderHelpers::addModifier($this->flags, Modifiers::PUBLIC);
|
||||
|
||||
return $this;
|
||||
}
|
||||
@ -50,7 +50,7 @@ class Property implements PhpParser\Builder
|
||||
* @return $this The builder instance (for fluid interface)
|
||||
*/
|
||||
public function makeProtected() {
|
||||
$this->flags = BuilderHelpers::addModifier($this->flags, Stmt\Class_::MODIFIER_PROTECTED);
|
||||
$this->flags = BuilderHelpers::addModifier($this->flags, Modifiers::PROTECTED);
|
||||
|
||||
return $this;
|
||||
}
|
||||
@ -61,7 +61,7 @@ class Property implements PhpParser\Builder
|
||||
* @return $this The builder instance (for fluid interface)
|
||||
*/
|
||||
public function makePrivate() {
|
||||
$this->flags = BuilderHelpers::addModifier($this->flags, Stmt\Class_::MODIFIER_PRIVATE);
|
||||
$this->flags = BuilderHelpers::addModifier($this->flags, Modifiers::PRIVATE);
|
||||
|
||||
return $this;
|
||||
}
|
||||
@ -72,7 +72,7 @@ class Property implements PhpParser\Builder
|
||||
* @return $this The builder instance (for fluid interface)
|
||||
*/
|
||||
public function makeStatic() {
|
||||
$this->flags = BuilderHelpers::addModifier($this->flags, Stmt\Class_::MODIFIER_STATIC);
|
||||
$this->flags = BuilderHelpers::addModifier($this->flags, Modifiers::STATIC);
|
||||
|
||||
return $this;
|
||||
}
|
||||
@ -83,7 +83,7 @@ class Property implements PhpParser\Builder
|
||||
* @return $this The builder instance (for fluid interface)
|
||||
*/
|
||||
public function makeReadonly() {
|
||||
$this->flags = BuilderHelpers::addModifier($this->flags, Stmt\Class_::MODIFIER_READONLY);
|
||||
$this->flags = BuilderHelpers::addModifier($this->flags, Modifiers::READONLY);
|
||||
|
||||
return $this;
|
||||
}
|
||||
@ -147,11 +147,11 @@ class Property implements PhpParser\Builder
|
||||
*
|
||||
* @return Stmt\Property The built property node
|
||||
*/
|
||||
public function getNode() : PhpParser\Node {
|
||||
public function getNode(): PhpParser\Node {
|
||||
return new Stmt\Property(
|
||||
$this->flags !== 0 ? $this->flags : Stmt\Class_::MODIFIER_PUBLIC,
|
||||
$this->flags !== 0 ? $this->flags : Modifiers::PUBLIC,
|
||||
[
|
||||
new Stmt\PropertyProperty($this->name, $this->default)
|
||||
new Node\PropertyItem($this->name, $this->default)
|
||||
],
|
||||
$this->attributes,
|
||||
$this->type,
|
||||
|
@ -7,8 +7,7 @@ use PhpParser\BuilderHelpers;
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Stmt;
|
||||
|
||||
class TraitUse implements Builder
|
||||
{
|
||||
class TraitUse implements Builder {
|
||||
protected $traits = [];
|
||||
protected $adaptations = [];
|
||||
|
||||
@ -58,7 +57,7 @@ class TraitUse implements Builder
|
||||
*
|
||||
* @return Node The built node
|
||||
*/
|
||||
public function getNode() : Node {
|
||||
public function getNode(): Node {
|
||||
return new Stmt\TraitUse($this->traits, $this->adaptations);
|
||||
}
|
||||
}
|
||||
|
@ -4,14 +4,14 @@ namespace PhpParser\Builder;
|
||||
|
||||
use PhpParser\Builder;
|
||||
use PhpParser\BuilderHelpers;
|
||||
use PhpParser\Modifiers;
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Stmt;
|
||||
|
||||
class TraitUseAdaptation implements Builder
|
||||
{
|
||||
const TYPE_UNDEFINED = 0;
|
||||
const TYPE_ALIAS = 1;
|
||||
const TYPE_PRECEDENCE = 2;
|
||||
class TraitUseAdaptation implements Builder {
|
||||
public const TYPE_UNDEFINED = 0;
|
||||
public const TYPE_ALIAS = 1;
|
||||
public const TYPE_PRECEDENCE = 2;
|
||||
|
||||
/** @var int Type of building adaptation */
|
||||
protected $type;
|
||||
@ -33,7 +33,7 @@ class TraitUseAdaptation implements Builder
|
||||
public function __construct($trait, $method) {
|
||||
$this->type = self::TYPE_UNDEFINED;
|
||||
|
||||
$this->trait = is_null($trait)? null: BuilderHelpers::normalizeName($trait);
|
||||
$this->trait = is_null($trait) ? null : BuilderHelpers::normalizeName($trait);
|
||||
$this->method = BuilderHelpers::normalizeIdentifier($method);
|
||||
}
|
||||
|
||||
@ -63,7 +63,7 @@ class TraitUseAdaptation implements Builder
|
||||
* @return $this The builder instance (for fluid interface)
|
||||
*/
|
||||
public function makePublic() {
|
||||
$this->setModifier(Stmt\Class_::MODIFIER_PUBLIC);
|
||||
$this->setModifier(Modifiers::PUBLIC);
|
||||
return $this;
|
||||
}
|
||||
|
||||
@ -73,7 +73,7 @@ class TraitUseAdaptation implements Builder
|
||||
* @return $this The builder instance (for fluid interface)
|
||||
*/
|
||||
public function makeProtected() {
|
||||
$this->setModifier(Stmt\Class_::MODIFIER_PROTECTED);
|
||||
$this->setModifier(Modifiers::PROTECTED);
|
||||
return $this;
|
||||
}
|
||||
|
||||
@ -83,7 +83,7 @@ class TraitUseAdaptation implements Builder
|
||||
* @return $this The builder instance (for fluid interface)
|
||||
*/
|
||||
public function makePrivate() {
|
||||
$this->setModifier(Stmt\Class_::MODIFIER_PRIVATE);
|
||||
$this->setModifier(Modifiers::PRIVATE);
|
||||
return $this;
|
||||
}
|
||||
|
||||
@ -135,7 +135,7 @@ class TraitUseAdaptation implements Builder
|
||||
*
|
||||
* @return Node The built node
|
||||
*/
|
||||
public function getNode() : Node {
|
||||
public function getNode(): Node {
|
||||
switch ($this->type) {
|
||||
case self::TYPE_ALIAS:
|
||||
return new Stmt\TraitUseAdaptation\Alias($this->trait, $this->method, $this->modifier, $this->alias);
|
||||
|
@ -7,8 +7,7 @@ use PhpParser\BuilderHelpers;
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Stmt;
|
||||
|
||||
class Trait_ extends Declaration
|
||||
{
|
||||
class Trait_ extends Declaration {
|
||||
protected $name;
|
||||
protected $uses = [];
|
||||
protected $properties = [];
|
||||
@ -67,7 +66,7 @@ class Trait_ extends Declaration
|
||||
*
|
||||
* @return Stmt\Trait_ The built interface node
|
||||
*/
|
||||
public function getNode() : PhpParser\Node {
|
||||
public function getNode(): PhpParser\Node {
|
||||
return new Stmt\Trait_(
|
||||
$this->name, [
|
||||
'stmts' => array_merge($this->uses, $this->properties, $this->methods),
|
||||
|
@ -7,8 +7,7 @@ use PhpParser\BuilderHelpers;
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Stmt;
|
||||
|
||||
class Use_ implements Builder
|
||||
{
|
||||
class Use_ implements Builder {
|
||||
protected $name;
|
||||
protected $type;
|
||||
protected $alias = null;
|
||||
@ -41,9 +40,9 @@ class Use_ implements Builder
|
||||
*
|
||||
* @return Stmt\Use_ The built node
|
||||
*/
|
||||
public function getNode() : Node {
|
||||
public function getNode(): Node {
|
||||
return new Stmt\Use_([
|
||||
new Stmt\UseUse($this->name, $this->alias)
|
||||
new Node\UseItem($this->name, $this->alias)
|
||||
], $this->type);
|
||||
}
|
||||
}
|
||||
|
@ -10,8 +10,7 @@ use PhpParser\Node\Name;
|
||||
use PhpParser\Node\Scalar\String_;
|
||||
use PhpParser\Node\Stmt\Use_;
|
||||
|
||||
class BuilderFactory
|
||||
{
|
||||
class BuilderFactory {
|
||||
/**
|
||||
* Creates an attribute node.
|
||||
*
|
||||
@ -20,7 +19,7 @@ class BuilderFactory
|
||||
*
|
||||
* @return Node\Attribute
|
||||
*/
|
||||
public function attribute($name, array $args = []) : Node\Attribute {
|
||||
public function attribute($name, array $args = []): Node\Attribute {
|
||||
return new Node\Attribute(
|
||||
BuilderHelpers::normalizeName($name),
|
||||
$this->args($args)
|
||||
@ -34,7 +33,7 @@ class BuilderFactory
|
||||
*
|
||||
* @return Builder\Namespace_ The created namespace builder
|
||||
*/
|
||||
public function namespace($name) : Builder\Namespace_ {
|
||||
public function namespace($name): Builder\Namespace_ {
|
||||
return new Builder\Namespace_($name);
|
||||
}
|
||||
|
||||
@ -45,7 +44,7 @@ class BuilderFactory
|
||||
*
|
||||
* @return Builder\Class_ The created class builder
|
||||
*/
|
||||
public function class(string $name) : Builder\Class_ {
|
||||
public function class(string $name): Builder\Class_ {
|
||||
return new Builder\Class_($name);
|
||||
}
|
||||
|
||||
@ -56,7 +55,7 @@ class BuilderFactory
|
||||
*
|
||||
* @return Builder\Interface_ The created interface builder
|
||||
*/
|
||||
public function interface(string $name) : Builder\Interface_ {
|
||||
public function interface(string $name): Builder\Interface_ {
|
||||
return new Builder\Interface_($name);
|
||||
}
|
||||
|
||||
@ -67,7 +66,7 @@ class BuilderFactory
|
||||
*
|
||||
* @return Builder\Trait_ The created trait builder
|
||||
*/
|
||||
public function trait(string $name) : Builder\Trait_ {
|
||||
public function trait(string $name): Builder\Trait_ {
|
||||
return new Builder\Trait_($name);
|
||||
}
|
||||
|
||||
@ -78,7 +77,7 @@ class BuilderFactory
|
||||
*
|
||||
* @return Builder\Enum_ The created enum builder
|
||||
*/
|
||||
public function enum(string $name) : Builder\Enum_ {
|
||||
public function enum(string $name): Builder\Enum_ {
|
||||
return new Builder\Enum_($name);
|
||||
}
|
||||
|
||||
@ -89,7 +88,7 @@ class BuilderFactory
|
||||
*
|
||||
* @return Builder\TraitUse The create trait use builder
|
||||
*/
|
||||
public function useTrait(...$traits) : Builder\TraitUse {
|
||||
public function useTrait(...$traits): Builder\TraitUse {
|
||||
return new Builder\TraitUse(...$traits);
|
||||
}
|
||||
|
||||
@ -101,7 +100,7 @@ class BuilderFactory
|
||||
*
|
||||
* @return Builder\TraitUseAdaptation The create trait use adaptation builder
|
||||
*/
|
||||
public function traitUseAdaptation($trait, $method = null) : Builder\TraitUseAdaptation {
|
||||
public function traitUseAdaptation($trait, $method = null): Builder\TraitUseAdaptation {
|
||||
if ($method === null) {
|
||||
$method = $trait;
|
||||
$trait = null;
|
||||
@ -117,7 +116,7 @@ class BuilderFactory
|
||||
*
|
||||
* @return Builder\Method The created method builder
|
||||
*/
|
||||
public function method(string $name) : Builder\Method {
|
||||
public function method(string $name): Builder\Method {
|
||||
return new Builder\Method($name);
|
||||
}
|
||||
|
||||
@ -128,7 +127,7 @@ class BuilderFactory
|
||||
*
|
||||
* @return Builder\Param The created parameter builder
|
||||
*/
|
||||
public function param(string $name) : Builder\Param {
|
||||
public function param(string $name): Builder\Param {
|
||||
return new Builder\Param($name);
|
||||
}
|
||||
|
||||
@ -139,7 +138,7 @@ class BuilderFactory
|
||||
*
|
||||
* @return Builder\Property The created property builder
|
||||
*/
|
||||
public function property(string $name) : Builder\Property {
|
||||
public function property(string $name): Builder\Property {
|
||||
return new Builder\Property($name);
|
||||
}
|
||||
|
||||
@ -150,7 +149,7 @@ class BuilderFactory
|
||||
*
|
||||
* @return Builder\Function_ The created function builder
|
||||
*/
|
||||
public function function(string $name) : Builder\Function_ {
|
||||
public function function(string $name): Builder\Function_ {
|
||||
return new Builder\Function_($name);
|
||||
}
|
||||
|
||||
@ -161,7 +160,7 @@ class BuilderFactory
|
||||
*
|
||||
* @return Builder\Use_ The created use builder
|
||||
*/
|
||||
public function use($name) : Builder\Use_ {
|
||||
public function use($name): Builder\Use_ {
|
||||
return new Builder\Use_($name, Use_::TYPE_NORMAL);
|
||||
}
|
||||
|
||||
@ -172,7 +171,7 @@ class BuilderFactory
|
||||
*
|
||||
* @return Builder\Use_ The created use function builder
|
||||
*/
|
||||
public function useFunction($name) : Builder\Use_ {
|
||||
public function useFunction($name): Builder\Use_ {
|
||||
return new Builder\Use_($name, Use_::TYPE_FUNCTION);
|
||||
}
|
||||
|
||||
@ -183,7 +182,7 @@ class BuilderFactory
|
||||
*
|
||||
* @return Builder\Use_ The created use const builder
|
||||
*/
|
||||
public function useConst($name) : Builder\Use_ {
|
||||
public function useConst($name): Builder\Use_ {
|
||||
return new Builder\Use_($name, Use_::TYPE_CONSTANT);
|
||||
}
|
||||
|
||||
@ -195,7 +194,7 @@ class BuilderFactory
|
||||
*
|
||||
* @return Builder\ClassConst The created use const builder
|
||||
*/
|
||||
public function classConst($name, $value) : Builder\ClassConst {
|
||||
public function classConst($name, $value): Builder\ClassConst {
|
||||
return new Builder\ClassConst($name, $value);
|
||||
}
|
||||
|
||||
@ -206,7 +205,7 @@ class BuilderFactory
|
||||
*
|
||||
* @return Builder\EnumCase The created use const builder
|
||||
*/
|
||||
public function enumCase($name) : Builder\EnumCase {
|
||||
public function enumCase($name): Builder\EnumCase {
|
||||
return new Builder\EnumCase($name);
|
||||
}
|
||||
|
||||
@ -217,7 +216,7 @@ class BuilderFactory
|
||||
*
|
||||
* @return Expr
|
||||
*/
|
||||
public function val($value) : Expr {
|
||||
public function val($value): Expr {
|
||||
return BuilderHelpers::normalizeValue($value);
|
||||
}
|
||||
|
||||
@ -228,7 +227,7 @@ class BuilderFactory
|
||||
*
|
||||
* @return Expr\Variable
|
||||
*/
|
||||
public function var($name) : Expr\Variable {
|
||||
public function var($name): Expr\Variable {
|
||||
if (!\is_string($name) && !$name instanceof Expr) {
|
||||
throw new \LogicException('Variable name must be string or Expr');
|
||||
}
|
||||
@ -245,7 +244,7 @@ class BuilderFactory
|
||||
*
|
||||
* @return Arg[]
|
||||
*/
|
||||
public function args(array $args) : array {
|
||||
public function args(array $args): array {
|
||||
$normalizedArgs = [];
|
||||
foreach ($args as $key => $arg) {
|
||||
if (!($arg instanceof Arg)) {
|
||||
@ -267,7 +266,7 @@ class BuilderFactory
|
||||
*
|
||||
* @return Expr\FuncCall
|
||||
*/
|
||||
public function funcCall($name, array $args = []) : Expr\FuncCall {
|
||||
public function funcCall($name, array $args = []): Expr\FuncCall {
|
||||
return new Expr\FuncCall(
|
||||
BuilderHelpers::normalizeNameOrExpr($name),
|
||||
$this->args($args)
|
||||
@ -283,7 +282,7 @@ class BuilderFactory
|
||||
*
|
||||
* @return Expr\MethodCall
|
||||
*/
|
||||
public function methodCall(Expr $var, $name, array $args = []) : Expr\MethodCall {
|
||||
public function methodCall(Expr $var, $name, array $args = []): Expr\MethodCall {
|
||||
return new Expr\MethodCall(
|
||||
$var,
|
||||
BuilderHelpers::normalizeIdentifierOrExpr($name),
|
||||
@ -300,7 +299,7 @@ class BuilderFactory
|
||||
*
|
||||
* @return Expr\StaticCall
|
||||
*/
|
||||
public function staticCall($class, $name, array $args = []) : Expr\StaticCall {
|
||||
public function staticCall($class, $name, array $args = []): Expr\StaticCall {
|
||||
return new Expr\StaticCall(
|
||||
BuilderHelpers::normalizeNameOrExpr($class),
|
||||
BuilderHelpers::normalizeIdentifierOrExpr($name),
|
||||
@ -316,7 +315,7 @@ class BuilderFactory
|
||||
*
|
||||
* @return Expr\New_
|
||||
*/
|
||||
public function new($class, array $args = []) : Expr\New_ {
|
||||
public function new($class, array $args = []): Expr\New_ {
|
||||
return new Expr\New_(
|
||||
BuilderHelpers::normalizeNameOrExpr($class),
|
||||
$this->args($args)
|
||||
@ -330,7 +329,7 @@ class BuilderFactory
|
||||
*
|
||||
* @return Expr\ConstFetch
|
||||
*/
|
||||
public function constFetch($name) : Expr\ConstFetch {
|
||||
public function constFetch($name): Expr\ConstFetch {
|
||||
return new Expr\ConstFetch(BuilderHelpers::normalizeName($name));
|
||||
}
|
||||
|
||||
@ -342,22 +341,22 @@ class BuilderFactory
|
||||
*
|
||||
* @return Expr\PropertyFetch
|
||||
*/
|
||||
public function propertyFetch(Expr $var, $name) : Expr\PropertyFetch {
|
||||
public function propertyFetch(Expr $var, $name): Expr\PropertyFetch {
|
||||
return new Expr\PropertyFetch($var, BuilderHelpers::normalizeIdentifierOrExpr($name));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a class constant fetch node.
|
||||
*
|
||||
* @param string|Name|Expr $class Class name
|
||||
* @param string|Identifier|Expr $name Constant name
|
||||
* @param string|Name|Expr $class Class name
|
||||
* @param string|Identifier $name Constant name
|
||||
*
|
||||
* @return Expr\ClassConstFetch
|
||||
*/
|
||||
public function classConstFetch($class, $name): Expr\ClassConstFetch {
|
||||
return new Expr\ClassConstFetch(
|
||||
BuilderHelpers::normalizeNameOrExpr($class),
|
||||
BuilderHelpers::normalizeIdentifierOrExpr($name)
|
||||
BuilderHelpers::normalizeIdentifier($name)
|
||||
);
|
||||
}
|
||||
|
||||
@ -368,7 +367,7 @@ class BuilderFactory
|
||||
*
|
||||
* @return Concat
|
||||
*/
|
||||
public function concat(...$exprs) : Concat {
|
||||
public function concat(...$exprs): Concat {
|
||||
$numExprs = count($exprs);
|
||||
if ($numExprs < 2) {
|
||||
throw new \LogicException('Expected at least two expressions');
|
||||
@ -385,7 +384,7 @@ class BuilderFactory
|
||||
* @param string|Expr $expr
|
||||
* @return Expr
|
||||
*/
|
||||
private function normalizeStringExpr($expr) : Expr {
|
||||
private function normalizeStringExpr($expr): Expr {
|
||||
if ($expr instanceof Expr) {
|
||||
return $expr;
|
||||
}
|
||||
|
@ -15,8 +15,7 @@ use PhpParser\Node\Stmt;
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class BuilderHelpers
|
||||
{
|
||||
final class BuilderHelpers {
|
||||
/**
|
||||
* Normalizes a node: Converts builder objects to nodes.
|
||||
*
|
||||
@ -24,7 +23,7 @@ final class BuilderHelpers
|
||||
*
|
||||
* @return Node The normalized node
|
||||
*/
|
||||
public static function normalizeNode($node) : Node {
|
||||
public static function normalizeNode($node): Node {
|
||||
if ($node instanceof Builder) {
|
||||
return $node->getNode();
|
||||
}
|
||||
@ -45,7 +44,7 @@ final class BuilderHelpers
|
||||
*
|
||||
* @return Stmt The normalized statement node
|
||||
*/
|
||||
public static function normalizeStmt($node) : Stmt {
|
||||
public static function normalizeStmt($node): Stmt {
|
||||
$node = self::normalizeNode($node);
|
||||
if ($node instanceof Stmt) {
|
||||
return $node;
|
||||
@ -65,7 +64,7 @@ final class BuilderHelpers
|
||||
*
|
||||
* @return Identifier The normalized identifier
|
||||
*/
|
||||
public static function normalizeIdentifier($name) : Identifier {
|
||||
public static function normalizeIdentifier($name): Identifier {
|
||||
if ($name instanceof Identifier) {
|
||||
return $name;
|
||||
}
|
||||
@ -103,7 +102,7 @@ final class BuilderHelpers
|
||||
*
|
||||
* @return Name The normalized name
|
||||
*/
|
||||
public static function normalizeName($name) : Name {
|
||||
public static function normalizeName($name): Name {
|
||||
if ($name instanceof Name) {
|
||||
return $name;
|
||||
}
|
||||
@ -219,7 +218,7 @@ final class BuilderHelpers
|
||||
*
|
||||
* @return Expr The normalized value
|
||||
*/
|
||||
public static function normalizeValue($value) : Expr {
|
||||
public static function normalizeValue($value): Expr {
|
||||
if ($value instanceof Node\Expr) {
|
||||
return $value;
|
||||
}
|
||||
@ -237,11 +236,11 @@ final class BuilderHelpers
|
||||
}
|
||||
|
||||
if (is_int($value)) {
|
||||
return new Scalar\LNumber($value);
|
||||
return new Scalar\Int_($value);
|
||||
}
|
||||
|
||||
if (is_float($value)) {
|
||||
return new Scalar\DNumber($value);
|
||||
return new Scalar\Float_($value);
|
||||
}
|
||||
|
||||
if (is_string($value)) {
|
||||
@ -254,12 +253,12 @@ final class BuilderHelpers
|
||||
foreach ($value as $itemKey => $itemValue) {
|
||||
// for consecutive, numeric keys don't generate keys
|
||||
if (null !== $lastKey && ++$lastKey === $itemKey) {
|
||||
$items[] = new Expr\ArrayItem(
|
||||
$items[] = new Node\ArrayItem(
|
||||
self::normalizeValue($itemValue)
|
||||
);
|
||||
} else {
|
||||
$lastKey = null;
|
||||
$items[] = new Expr\ArrayItem(
|
||||
$items[] = new Node\ArrayItem(
|
||||
self::normalizeValue($itemValue),
|
||||
self::normalizeValue($itemKey)
|
||||
);
|
||||
@ -279,7 +278,7 @@ final class BuilderHelpers
|
||||
*
|
||||
* @return Comment\Doc The normalized doc comment
|
||||
*/
|
||||
public static function normalizeDocComment($docComment) : Comment\Doc {
|
||||
public static function normalizeDocComment($docComment): Comment\Doc {
|
||||
if ($docComment instanceof Comment\Doc) {
|
||||
return $docComment;
|
||||
}
|
||||
@ -298,8 +297,7 @@ final class BuilderHelpers
|
||||
*
|
||||
* @return Node\AttributeGroup The Attribute Group
|
||||
*/
|
||||
public static function normalizeAttribute($attribute) : Node\AttributeGroup
|
||||
{
|
||||
public static function normalizeAttribute($attribute): Node\AttributeGroup {
|
||||
if ($attribute instanceof Node\AttributeGroup) {
|
||||
return $attribute;
|
||||
}
|
||||
@ -319,7 +317,7 @@ final class BuilderHelpers
|
||||
*
|
||||
* @return int New modifiers
|
||||
*/
|
||||
public static function addModifier(int $modifiers, int $modifier) : int {
|
||||
public static function addModifier(int $modifiers, int $modifier): int {
|
||||
Stmt\Class_::verifyModifier($modifiers, $modifier);
|
||||
return $modifiers | $modifier;
|
||||
}
|
||||
@ -328,7 +326,7 @@ final class BuilderHelpers
|
||||
* Adds a modifier and returns new modifier bitmask.
|
||||
* @return int New modifiers
|
||||
*/
|
||||
public static function addClassModifier(int $existingModifiers, int $modifierToSet) : int {
|
||||
public static function addClassModifier(int $existingModifiers, int $modifierToSet): int {
|
||||
Stmt\Class_::verifyClassModifier($existingModifiers, $modifierToSet);
|
||||
return $existingModifiers | $modifierToSet;
|
||||
}
|
||||
|
@ -2,8 +2,7 @@
|
||||
|
||||
namespace PhpParser;
|
||||
|
||||
class Comment implements \JsonSerializable
|
||||
{
|
||||
class Comment implements \JsonSerializable {
|
||||
protected $text;
|
||||
protected $startLine;
|
||||
protected $startFilePos;
|
||||
@ -39,7 +38,7 @@ class Comment implements \JsonSerializable
|
||||
*
|
||||
* @return string The comment text (including comment delimiters like /*)
|
||||
*/
|
||||
public function getText() : string {
|
||||
public function getText(): string {
|
||||
return $this->text;
|
||||
}
|
||||
|
||||
@ -48,7 +47,7 @@ class Comment implements \JsonSerializable
|
||||
*
|
||||
* @return int Line number (or -1 if not available)
|
||||
*/
|
||||
public function getStartLine() : int {
|
||||
public function getStartLine(): int {
|
||||
return $this->startLine;
|
||||
}
|
||||
|
||||
@ -57,7 +56,7 @@ class Comment implements \JsonSerializable
|
||||
*
|
||||
* @return int File offset (or -1 if not available)
|
||||
*/
|
||||
public function getStartFilePos() : int {
|
||||
public function getStartFilePos(): int {
|
||||
return $this->startFilePos;
|
||||
}
|
||||
|
||||
@ -66,7 +65,7 @@ class Comment implements \JsonSerializable
|
||||
*
|
||||
* @return int Token offset (or -1 if not available)
|
||||
*/
|
||||
public function getStartTokenPos() : int {
|
||||
public function getStartTokenPos(): int {
|
||||
return $this->startTokenPos;
|
||||
}
|
||||
|
||||
@ -75,7 +74,7 @@ class Comment implements \JsonSerializable
|
||||
*
|
||||
* @return int Line number (or -1 if not available)
|
||||
*/
|
||||
public function getEndLine() : int {
|
||||
public function getEndLine(): int {
|
||||
return $this->endLine;
|
||||
}
|
||||
|
||||
@ -84,7 +83,7 @@ class Comment implements \JsonSerializable
|
||||
*
|
||||
* @return int File offset (or -1 if not available)
|
||||
*/
|
||||
public function getEndFilePos() : int {
|
||||
public function getEndFilePos(): int {
|
||||
return $this->endFilePos;
|
||||
}
|
||||
|
||||
@ -93,7 +92,7 @@ class Comment implements \JsonSerializable
|
||||
*
|
||||
* @return int Token offset (or -1 if not available)
|
||||
*/
|
||||
public function getEndTokenPos() : int {
|
||||
public function getEndTokenPos(): int {
|
||||
return $this->endTokenPos;
|
||||
}
|
||||
|
||||
@ -104,7 +103,7 @@ class Comment implements \JsonSerializable
|
||||
*
|
||||
* @return int Line number
|
||||
*/
|
||||
public function getLine() : int {
|
||||
public function getLine(): int {
|
||||
return $this->startLine;
|
||||
}
|
||||
|
||||
@ -115,7 +114,7 @@ class Comment implements \JsonSerializable
|
||||
*
|
||||
* @return int File offset
|
||||
*/
|
||||
public function getFilePos() : int {
|
||||
public function getFilePos(): int {
|
||||
return $this->startFilePos;
|
||||
}
|
||||
|
||||
@ -126,7 +125,7 @@ class Comment implements \JsonSerializable
|
||||
*
|
||||
* @return int Token offset
|
||||
*/
|
||||
public function getTokenPos() : int {
|
||||
public function getTokenPos(): int {
|
||||
return $this->startTokenPos;
|
||||
}
|
||||
|
||||
@ -135,7 +134,7 @@ class Comment implements \JsonSerializable
|
||||
*
|
||||
* @return string The comment text (including comment delimiters like /*)
|
||||
*/
|
||||
public function __toString() : string {
|
||||
public function __toString(): string {
|
||||
return $this->text;
|
||||
}
|
||||
|
||||
@ -150,12 +149,13 @@ class Comment implements \JsonSerializable
|
||||
* @return mixed|string
|
||||
*/
|
||||
public function getReformattedText() {
|
||||
$text = trim($this->text);
|
||||
$text = $this->text;
|
||||
$newlinePos = strpos($text, "\n");
|
||||
if (false === $newlinePos) {
|
||||
// Single line comments don't need further processing
|
||||
return $text;
|
||||
} elseif (preg_match('((*BSR_ANYCRLF)(*ANYCRLF)^.*(?:\R\s+\*.*)+$)', $text)) {
|
||||
}
|
||||
if (preg_match('((*BSR_ANYCRLF)(*ANYCRLF)^.*(?:\R\s+\*.*)+$)', $text)) {
|
||||
// Multi line comment of the type
|
||||
//
|
||||
// /*
|
||||
@ -165,7 +165,8 @@ class Comment implements \JsonSerializable
|
||||
//
|
||||
// is handled by replacing the whitespace sequences before the * by a single space
|
||||
return preg_replace('(^\s+\*)m', ' *', $this->text);
|
||||
} elseif (preg_match('(^/\*\*?\s*[\r\n])', $text) && preg_match('(\n(\s*)\*/$)', $text, $matches)) {
|
||||
}
|
||||
if (preg_match('(^/\*\*?\s*[\r\n])', $text) && preg_match('(\n(\s*)\*/$)', $text, $matches)) {
|
||||
// Multi line comment of the type
|
||||
//
|
||||
// /*
|
||||
@ -177,7 +178,8 @@ class Comment implements \JsonSerializable
|
||||
// */ on all lines. So if the last line is " */", then " " is removed at the
|
||||
// start of all lines.
|
||||
return preg_replace('(^' . preg_quote($matches[1]) . ')m', '', $text);
|
||||
} elseif (preg_match('(^/\*\*?\s*(?!\s))', $text, $matches)) {
|
||||
}
|
||||
if (preg_match('(^/\*\*?\s*(?!\s))', $text, $matches)) {
|
||||
// Multi line comment of the type
|
||||
//
|
||||
// /* Some text.
|
||||
@ -204,7 +206,7 @@ class Comment implements \JsonSerializable
|
||||
* @param string $str String to check
|
||||
* @return int Length in characters. Tabs count as single characters.
|
||||
*/
|
||||
private function getShortestWhitespacePrefixLen(string $str) : int {
|
||||
private function getShortestWhitespacePrefixLen(string $str): int {
|
||||
$lines = explode("\n", $str);
|
||||
$shortestPrefixLen = \INF;
|
||||
foreach ($lines as $line) {
|
||||
@ -221,7 +223,7 @@ class Comment implements \JsonSerializable
|
||||
* @return array
|
||||
* @psalm-return array{nodeType:string, text:mixed, line:mixed, filePos:mixed}
|
||||
*/
|
||||
public function jsonSerialize() : array {
|
||||
public function jsonSerialize(): array {
|
||||
// Technically not a node, but we make it look like one anyway
|
||||
$type = $this instanceof Comment\Doc ? 'Comment_Doc' : 'Comment';
|
||||
return [
|
||||
|
@ -2,6 +2,5 @@
|
||||
|
||||
namespace PhpParser\Comment;
|
||||
|
||||
class Doc extends \PhpParser\Comment
|
||||
{
|
||||
class Doc extends \PhpParser\Comment {
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser;
|
||||
|
||||
class ConstExprEvaluationException extends \Exception
|
||||
{}
|
||||
class ConstExprEvaluationException extends \Exception {
|
||||
}
|
||||
|
@ -1,11 +1,12 @@
|
||||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser;
|
||||
|
||||
use function array_merge;
|
||||
use PhpParser\Node\Expr;
|
||||
use PhpParser\Node\Scalar;
|
||||
|
||||
use function array_merge;
|
||||
|
||||
/**
|
||||
* Evaluates constant expressions.
|
||||
*
|
||||
@ -25,8 +26,7 @@ use PhpParser\Node\Scalar;
|
||||
* point to string conversions are affected by the precision ini setting. Secondly, they are also
|
||||
* affected by the LC_NUMERIC locale.
|
||||
*/
|
||||
class ConstExprEvaluator
|
||||
{
|
||||
class ConstExprEvaluator {
|
||||
private $fallbackEvaluator;
|
||||
|
||||
/**
|
||||
@ -37,8 +37,8 @@ class ConstExprEvaluator
|
||||
*
|
||||
* @param callable|null $fallbackEvaluator To call if subexpression cannot be evaluated
|
||||
*/
|
||||
public function __construct(callable $fallbackEvaluator = null) {
|
||||
$this->fallbackEvaluator = $fallbackEvaluator ?? function(Expr $expr) {
|
||||
public function __construct(?callable $fallbackEvaluator = null) {
|
||||
$this->fallbackEvaluator = $fallbackEvaluator ?? function (Expr $expr) {
|
||||
throw new ConstExprEvaluationException(
|
||||
"Expression of type {$expr->getType()} cannot be evaluated"
|
||||
);
|
||||
@ -63,7 +63,7 @@ class ConstExprEvaluator
|
||||
* @throws ConstExprEvaluationException if the expression cannot be evaluated or an error occurred
|
||||
*/
|
||||
public function evaluateSilently(Expr $expr) {
|
||||
set_error_handler(function($num, $str, $file, $line) {
|
||||
set_error_handler(function ($num, $str, $file, $line) {
|
||||
throw new \ErrorException($str, 0, $num, $file, $line);
|
||||
});
|
||||
|
||||
@ -102,8 +102,8 @@ class ConstExprEvaluator
|
||||
}
|
||||
|
||||
private function evaluate(Expr $expr) {
|
||||
if ($expr instanceof Scalar\LNumber
|
||||
|| $expr instanceof Scalar\DNumber
|
||||
if ($expr instanceof Scalar\Int_
|
||||
|| $expr instanceof Scalar\Float_
|
||||
|| $expr instanceof Scalar\String_
|
||||
) {
|
||||
return $expr->value;
|
||||
|
@ -2,8 +2,7 @@
|
||||
|
||||
namespace PhpParser;
|
||||
|
||||
class Error extends \RuntimeException
|
||||
{
|
||||
class Error extends \RuntimeException {
|
||||
protected $rawMessage;
|
||||
protected $attributes;
|
||||
|
||||
@ -29,7 +28,7 @@ class Error extends \RuntimeException
|
||||
*
|
||||
* @return string Error message
|
||||
*/
|
||||
public function getRawMessage() : string {
|
||||
public function getRawMessage(): string {
|
||||
return $this->rawMessage;
|
||||
}
|
||||
|
||||
@ -38,7 +37,7 @@ class Error extends \RuntimeException
|
||||
*
|
||||
* @return int Error start line
|
||||
*/
|
||||
public function getStartLine() : int {
|
||||
public function getStartLine(): int {
|
||||
return $this->attributes['startLine'] ?? -1;
|
||||
}
|
||||
|
||||
@ -47,7 +46,7 @@ class Error extends \RuntimeException
|
||||
*
|
||||
* @return int Error end line
|
||||
*/
|
||||
public function getEndLine() : int {
|
||||
public function getEndLine(): int {
|
||||
return $this->attributes['endLine'] ?? -1;
|
||||
}
|
||||
|
||||
@ -56,7 +55,7 @@ class Error extends \RuntimeException
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getAttributes() : array {
|
||||
public function getAttributes(): array {
|
||||
return $this->attributes;
|
||||
}
|
||||
|
||||
@ -97,7 +96,7 @@ class Error extends \RuntimeException
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasColumnInfo() : bool {
|
||||
public function hasColumnInfo(): bool {
|
||||
return isset($this->attributes['startFilePos'], $this->attributes['endFilePos']);
|
||||
}
|
||||
|
||||
@ -107,7 +106,7 @@ class Error extends \RuntimeException
|
||||
* @param string $code Source code of the file
|
||||
* @return int
|
||||
*/
|
||||
public function getStartColumn(string $code) : int {
|
||||
public function getStartColumn(string $code): int {
|
||||
if (!$this->hasColumnInfo()) {
|
||||
throw new \RuntimeException('Error does not have column information');
|
||||
}
|
||||
@ -121,7 +120,7 @@ class Error extends \RuntimeException
|
||||
* @param string $code Source code of the file
|
||||
* @return int
|
||||
*/
|
||||
public function getEndColumn(string $code) : int {
|
||||
public function getEndColumn(string $code): int {
|
||||
if (!$this->hasColumnInfo()) {
|
||||
throw new \RuntimeException('Error does not have column information');
|
||||
}
|
||||
@ -136,7 +135,7 @@ class Error extends \RuntimeException
|
||||
*
|
||||
* @return string Formatted message
|
||||
*/
|
||||
public function getMessageWithColumnInfo(string $code) : string {
|
||||
public function getMessageWithColumnInfo(string $code): string {
|
||||
return sprintf(
|
||||
'%s from %d:%d to %d:%d', $this->getRawMessage(),
|
||||
$this->getStartLine(), $this->getStartColumn($code),
|
||||
@ -152,7 +151,7 @@ class Error extends \RuntimeException
|
||||
*
|
||||
* @return int 1-based column (relative to start of line)
|
||||
*/
|
||||
private function toColumn(string $code, int $pos) : int {
|
||||
private function toColumn(string $code, int $pos): int {
|
||||
if ($pos > strlen($code)) {
|
||||
throw new \RuntimeException('Invalid position information');
|
||||
}
|
||||
|
@ -2,8 +2,7 @@
|
||||
|
||||
namespace PhpParser;
|
||||
|
||||
interface ErrorHandler
|
||||
{
|
||||
interface ErrorHandler {
|
||||
/**
|
||||
* Handle an error generated during lexing, parsing or some other operation.
|
||||
*
|
||||
|
@ -10,8 +10,7 @@ use PhpParser\ErrorHandler;
|
||||
*
|
||||
* This allows graceful handling of errors.
|
||||
*/
|
||||
class Collecting implements ErrorHandler
|
||||
{
|
||||
class Collecting implements ErrorHandler {
|
||||
/** @var Error[] Collected errors */
|
||||
private $errors = [];
|
||||
|
||||
@ -24,7 +23,7 @@ class Collecting implements ErrorHandler
|
||||
*
|
||||
* @return Error[]
|
||||
*/
|
||||
public function getErrors() : array {
|
||||
public function getErrors(): array {
|
||||
return $this->errors;
|
||||
}
|
||||
|
||||
@ -33,7 +32,7 @@ class Collecting implements ErrorHandler
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasErrors() : bool {
|
||||
public function hasErrors(): bool {
|
||||
return !empty($this->errors);
|
||||
}
|
||||
|
||||
|
@ -10,8 +10,7 @@ use PhpParser\ErrorHandler;
|
||||
*
|
||||
* This is the default strategy used by all components.
|
||||
*/
|
||||
class Throwing implements ErrorHandler
|
||||
{
|
||||
class Throwing implements ErrorHandler {
|
||||
public function handleError(Error $error) {
|
||||
throw $error;
|
||||
}
|
||||
|
@ -5,12 +5,11 @@ namespace PhpParser\Internal;
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class DiffElem
|
||||
{
|
||||
const TYPE_KEEP = 0;
|
||||
const TYPE_REMOVE = 1;
|
||||
const TYPE_ADD = 2;
|
||||
const TYPE_REPLACE = 3;
|
||||
class DiffElem {
|
||||
public const TYPE_KEEP = 0;
|
||||
public const TYPE_REMOVE = 1;
|
||||
public const TYPE_ADD = 2;
|
||||
public const TYPE_REPLACE = 3;
|
||||
|
||||
/** @var int One of the TYPE_* constants */
|
||||
public $type;
|
||||
|
@ -10,8 +10,7 @@ namespace PhpParser\Internal;
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class Differ
|
||||
{
|
||||
class Differ {
|
||||
private $isEqual;
|
||||
|
||||
/**
|
||||
@ -31,7 +30,7 @@ class Differ
|
||||
*
|
||||
* @return DiffElem[] Diff (edit script)
|
||||
*/
|
||||
public function diff(array $old, array $new) {
|
||||
public function diff(array $old, array $new): array {
|
||||
list($trace, $x, $y) = $this->calculateTrace($old, $new);
|
||||
return $this->extractDiff($trace, $x, $y, $old, $new);
|
||||
}
|
||||
@ -47,7 +46,7 @@ class Differ
|
||||
*
|
||||
* @return DiffElem[] Diff (edit script), including replace operations
|
||||
*/
|
||||
public function diffWithReplacements(array $old, array $new) {
|
||||
public function diffWithReplacements(array $old, array $new): array {
|
||||
return $this->coalesceReplacements($this->diff($old, $new));
|
||||
}
|
||||
|
||||
@ -125,7 +124,7 @@ class Differ
|
||||
* @param DiffElem[] $diff
|
||||
* @return DiffElem[]
|
||||
*/
|
||||
private function coalesceReplacements(array $diff) {
|
||||
private function coalesceReplacements(array $diff): array {
|
||||
$newDiff = [];
|
||||
$c = \count($diff);
|
||||
for ($i = 0; $i < $c; $i++) {
|
||||
|
@ -15,12 +15,9 @@ use PhpParser\Node\Expr;
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class PrintableNewAnonClassNode extends Expr
|
||||
{
|
||||
class PrintableNewAnonClassNode extends Expr {
|
||||
/** @var Node\AttributeGroup[] PHP attribute groups */
|
||||
public $attrGroups;
|
||||
/** @var int Modifiers */
|
||||
public $flags;
|
||||
/** @var Node\Arg[] Arguments */
|
||||
public $args;
|
||||
/** @var null|Node\Name Name of extended class */
|
||||
@ -31,12 +28,11 @@ class PrintableNewAnonClassNode extends Expr
|
||||
public $stmts;
|
||||
|
||||
public function __construct(
|
||||
array $attrGroups, int $flags, array $args, Node\Name $extends = null, array $implements,
|
||||
array $attrGroups, array $args, ?Node\Name $extends, array $implements,
|
||||
array $stmts, array $attributes
|
||||
) {
|
||||
parent::__construct($attributes);
|
||||
$this->attrGroups = $attrGroups;
|
||||
$this->flags = $flags;
|
||||
$this->args = $args;
|
||||
$this->extends = $extends;
|
||||
$this->implements = $implements;
|
||||
@ -49,16 +45,16 @@ class PrintableNewAnonClassNode extends Expr
|
||||
// We don't assert that $class->name is null here, to allow consumers to assign unique names
|
||||
// to anonymous classes for their own purposes. We simplify ignore the name here.
|
||||
return new self(
|
||||
$class->attrGroups, $class->flags, $newNode->args, $class->extends, $class->implements,
|
||||
$class->attrGroups, $newNode->args, $class->extends, $class->implements,
|
||||
$class->stmts, $newNode->getAttributes()
|
||||
);
|
||||
}
|
||||
|
||||
public function getType() : string {
|
||||
public function getType(): string {
|
||||
return 'Expr_PrintableNewAnonClass';
|
||||
}
|
||||
|
||||
public function getSubNodeNames() : array {
|
||||
return ['attrGroups', 'flags', 'args', 'extends', 'implements', 'stmts'];
|
||||
public function getSubNodeNames(): array {
|
||||
return ['attrGroups', 'args', 'extends', 'implements', 'stmts'];
|
||||
}
|
||||
}
|
||||
|
272
lib/PhpParser/Internal/TokenPolyfill.php
Normal file
272
lib/PhpParser/Internal/TokenPolyfill.php
Normal file
@ -0,0 +1,272 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser\Internal;
|
||||
|
||||
if (\PHP_VERSION_ID >= 80000) {
|
||||
class TokenPolyfill extends \PhpToken {
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* This is a polyfill for the PhpToken class introduced in PHP 8.0. We do not actually polyfill
|
||||
* PhpToken, because composer might end up picking a different polyfill implementation, which does
|
||||
* not meet our requirements.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class TokenPolyfill {
|
||||
/** @var int The ID of the token. Either a T_* constant of a character code < 256. */
|
||||
public $id;
|
||||
/** @var string The textual content of the token. */
|
||||
public $text;
|
||||
/** @var int The 1-based starting line of the token (or -1 if unknown). */
|
||||
public $line;
|
||||
/** @var int The 0-based starting position of the token (or -1 if unknown). */
|
||||
public $pos;
|
||||
|
||||
/** @var bool[] Tokens ignored by the PHP parser. */
|
||||
private const IGNORABLE_TOKENS = [
|
||||
\T_WHITESPACE => true,
|
||||
\T_COMMENT => true,
|
||||
\T_DOC_COMMENT => true,
|
||||
\T_OPEN_TAG => true,
|
||||
];
|
||||
|
||||
/** @var bool[] Tokens that may be part of a T_NAME_* identifier. */
|
||||
private static $identifierTokens;
|
||||
|
||||
/**
|
||||
* Create a Token with the given ID and text, as well optional line and position information.
|
||||
*/
|
||||
final public function __construct(int $id, string $text, int $line = -1, int $pos = -1) {
|
||||
$this->id = $id;
|
||||
$this->text = $text;
|
||||
$this->line = $line;
|
||||
$this->pos = $pos;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name of the token. For single-char tokens this will be the token character.
|
||||
* Otherwise it will be a T_* style name, or null if the token ID is unknown.
|
||||
*/
|
||||
public function getTokenName(): ?string {
|
||||
if ($this->id < 256) {
|
||||
return \chr($this->id);
|
||||
}
|
||||
|
||||
$name = token_name($this->id);
|
||||
return $name === 'UNKNOWN' ? null : $name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the token is of the given kind. The kind may be either an integer that matches
|
||||
* the token ID, a string that matches the token text, or an array of integers/strings. In the
|
||||
* latter case, the function returns true if any of the kinds in the array match.
|
||||
*
|
||||
* @param int|string|array $kind
|
||||
*/
|
||||
public function is($kind): bool {
|
||||
if (\is_int($kind)) {
|
||||
return $this->id === $kind;
|
||||
}
|
||||
if (\is_string($kind)) {
|
||||
return $this->text === $kind;
|
||||
}
|
||||
if (\is_array($kind)) {
|
||||
foreach ($kind as $entry) {
|
||||
if (\is_int($entry)) {
|
||||
if ($this->id === $entry) {
|
||||
return true;
|
||||
}
|
||||
} elseif (\is_string($entry)) {
|
||||
if ($this->text === $entry) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
throw new \TypeError(
|
||||
'Argument #1 ($kind) must only have elements of type string|int, ' .
|
||||
gettype($entry) . ' given');
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
throw new \TypeError(
|
||||
'Argument #1 ($kind) must be of type string|int|array, ' .gettype($kind) . ' given');
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether this token would be ignored by the PHP parser. Returns true for T_WHITESPACE,
|
||||
* T_COMMENT, T_DOC_COMMENT and T_OPEN_TAG, and false for everything else.
|
||||
*/
|
||||
public function isIgnorable(): bool {
|
||||
return isset(self::IGNORABLE_TOKENS[$this->id]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the textual content of the token.
|
||||
*/
|
||||
public function __toString(): string {
|
||||
return $this->text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tokenize the given source code and return an array of tokens.
|
||||
*
|
||||
* This performs certain canonicalizations to match the PHP 8.0 token format:
|
||||
* * Bad characters are represented using T_BAD_CHARACTER rather than omitted.
|
||||
* * T_COMMENT does not include trailing newlines, instead the newline is part of a following
|
||||
* T_WHITESPACE token.
|
||||
* * Namespaced names are represented using T_NAME_* tokens.
|
||||
*
|
||||
* @returns static[]
|
||||
*/
|
||||
public static function tokenize(string $code, int $flags = 0): array {
|
||||
self::init();
|
||||
|
||||
$tokens = [];
|
||||
$line = 1;
|
||||
$pos = 0;
|
||||
$origTokens = \token_get_all($code, $flags);
|
||||
if (\PHP_VERSION_ID < 70400) {
|
||||
$origTokens = self::fixupBadCharacters($code, $origTokens);
|
||||
}
|
||||
|
||||
$numTokens = \count($origTokens);
|
||||
for ($i = 0; $i < $numTokens; $i++) {
|
||||
$token = $origTokens[$i];
|
||||
if (\is_string($token)) {
|
||||
if (\strlen($token) === 2) {
|
||||
// b" and B" are tokenized as single-char tokens, even though they aren't.
|
||||
$tokens[] = new static(\ord('"'), $token, $line, $pos);
|
||||
$pos += 2;
|
||||
} else {
|
||||
$tokens[] = new static(\ord($token), $token, $line, $pos);
|
||||
$pos++;
|
||||
}
|
||||
} else {
|
||||
$id = $token[0];
|
||||
$text = $token[1];
|
||||
|
||||
// Emulate PHP 8.0 comment format, which does not include trailing whitespace anymore.
|
||||
if ($id === \T_COMMENT && \substr($text, 0, 2) !== '/*' &&
|
||||
\preg_match('/(\r\n|\n|\r)$/D', $text, $matches)
|
||||
) {
|
||||
$trailingNewline = $matches[0];
|
||||
$text = \substr($text, 0, -\strlen($trailingNewline));
|
||||
$tokens[] = new static($id, $text, $line, $pos);
|
||||
$pos += \strlen($text);
|
||||
|
||||
if ($i + 1 < $numTokens && $origTokens[$i + 1][0] === \T_WHITESPACE) {
|
||||
// Move trailing newline into following T_WHITESPACE token, if it already exists.
|
||||
$origTokens[$i + 1][1] = $trailingNewline . $origTokens[$i + 1][1];
|
||||
$origTokens[$i + 1][2]--;
|
||||
} else {
|
||||
// Otherwise, we need to create a new T_WHITESPACE token.
|
||||
$tokens[] = new static(\T_WHITESPACE, $trailingNewline, $line, $pos);
|
||||
$line++;
|
||||
$pos += \strlen($trailingNewline);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// Emulate PHP 8.0 T_NAME_* tokens, by combining sequences of T_NS_SEPARATOR and
|
||||
// T_STRING into a single token.
|
||||
if (($id === \T_NS_SEPARATOR || isset(self::$identifierTokens[$id]))) {
|
||||
$newText = $text;
|
||||
$lastWasSeparator = $id === \T_NS_SEPARATOR;
|
||||
for ($j = $i + 1; $j < $numTokens; $j++) {
|
||||
if ($lastWasSeparator) {
|
||||
if (!isset(self::$identifierTokens[$origTokens[$j][0]])) {
|
||||
break;
|
||||
}
|
||||
$lastWasSeparator = false;
|
||||
} else {
|
||||
if ($origTokens[$j][0] !== \T_NS_SEPARATOR) {
|
||||
break;
|
||||
}
|
||||
$lastWasSeparator = true;
|
||||
}
|
||||
$newText .= $origTokens[$j][1];
|
||||
}
|
||||
if ($lastWasSeparator) {
|
||||
// Trailing separator is not part of the name.
|
||||
$j--;
|
||||
$newText = \substr($newText, 0, -1);
|
||||
}
|
||||
if ($j > $i + 1) {
|
||||
if ($id === \T_NS_SEPARATOR) {
|
||||
$id = \T_NAME_FULLY_QUALIFIED;
|
||||
} elseif ($id === \T_NAMESPACE) {
|
||||
$id = \T_NAME_RELATIVE;
|
||||
} else {
|
||||
$id = \T_NAME_QUALIFIED;
|
||||
}
|
||||
$tokens[] = new static($id, $newText, $line, $pos);
|
||||
$pos += \strlen($newText);
|
||||
$i = $j - 1;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
$tokens[] = new static($id, $text, $line, $pos);
|
||||
$line += \substr_count($text, "\n");
|
||||
$pos += \strlen($text);
|
||||
}
|
||||
}
|
||||
return $tokens;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prior to PHP 7.4, token_get_all() simply dropped invalid characters from the token stream.
|
||||
* Detect such cases and replace them with T_BAD_CHARACTER.
|
||||
*/
|
||||
private static function fixupBadCharacters(string $code, array $origTokens): array {
|
||||
$newTokens = [];
|
||||
$pos = 0;
|
||||
foreach ($origTokens as $token) {
|
||||
$text = \is_string($token) ? $token : $token[1];
|
||||
$len = \strlen($text);
|
||||
if (substr($code, $pos, $len) !== $text) {
|
||||
$nextPos = strpos($code, $text, $pos);
|
||||
for ($i = $pos; $i < $nextPos; $i++) {
|
||||
// Don't bother including the line, we're not going to use it anyway.
|
||||
$newTokens[] = [\T_BAD_CHARACTER, $code[$i]];
|
||||
}
|
||||
$pos = $nextPos;
|
||||
}
|
||||
$pos += $len;
|
||||
$newTokens[] = $token;
|
||||
}
|
||||
|
||||
// Handle trailing invalid characters.
|
||||
$codeLen = \strlen($code);
|
||||
if ($pos !== $codeLen) {
|
||||
for ($i = $pos; $i < $codeLen; $i++) {
|
||||
$newTokens[] = [\T_BAD_CHARACTER, $code[$i]];
|
||||
}
|
||||
}
|
||||
return $newTokens;
|
||||
}
|
||||
|
||||
/** Initialize private static state needed by tokenize(). */
|
||||
private static function init(): void {
|
||||
if (isset(self::$identifierTokens)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Based on semi_reserved production.
|
||||
self::$identifierTokens = \array_fill_keys([
|
||||
\T_STRING,
|
||||
\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,
|
||||
\T_FINALLY, \T_THROW, \T_USE, \T_INSTEADOF, \T_GLOBAL, \T_VAR, \T_UNSET, \T_ISSET, \T_EMPTY, \T_CONTINUE, \T_GOTO,
|
||||
\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,
|
||||
], true);
|
||||
}
|
||||
}
|
@ -2,14 +2,15 @@
|
||||
|
||||
namespace PhpParser\Internal;
|
||||
|
||||
use PhpParser\Token;
|
||||
|
||||
/**
|
||||
* Provides operations on token streams, for use by pretty printer.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class TokenStream
|
||||
{
|
||||
/** @var array Tokens (in token_get_all format) */
|
||||
class TokenStream {
|
||||
/** @var Token[] Tokens (in PhpToken::tokenize() format) */
|
||||
private $tokens;
|
||||
/** @var int[] Map from position to indentation */
|
||||
private $indentMap;
|
||||
@ -17,7 +18,7 @@ class TokenStream
|
||||
/**
|
||||
* Create token stream instance.
|
||||
*
|
||||
* @param array $tokens Tokens in token_get_all() format
|
||||
* @param Token[] $tokens Tokens in PhpToken::tokenize() format
|
||||
*/
|
||||
public function __construct(array $tokens) {
|
||||
$this->tokens = $tokens;
|
||||
@ -32,7 +33,7 @@ class TokenStream
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function haveParens(int $startPos, int $endPos) : bool {
|
||||
public function haveParens(int $startPos, int $endPos): bool {
|
||||
return $this->haveTokenImmediatelyBefore($startPos, '(')
|
||||
&& $this->haveTokenImmediatelyAfter($endPos, ')');
|
||||
}
|
||||
@ -45,7 +46,7 @@ class TokenStream
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function haveBraces(int $startPos, int $endPos) : bool {
|
||||
public function haveBraces(int $startPos, int $endPos): bool {
|
||||
return ($this->haveTokenImmediatelyBefore($startPos, '{')
|
||||
|| $this->haveTokenImmediatelyBefore($startPos, T_CURLY_OPEN))
|
||||
&& $this->haveTokenImmediatelyAfter($endPos, '}');
|
||||
@ -61,16 +62,15 @@ class TokenStream
|
||||
*
|
||||
* @return bool Whether the expected token was found
|
||||
*/
|
||||
public function haveTokenImmediatelyBefore(int $pos, $expectedTokenType) : bool {
|
||||
public function haveTokenImmediatelyBefore(int $pos, $expectedTokenType): bool {
|
||||
$tokens = $this->tokens;
|
||||
$pos--;
|
||||
for (; $pos >= 0; $pos--) {
|
||||
$tokenType = $tokens[$pos][0];
|
||||
if ($tokenType === $expectedTokenType) {
|
||||
$token = $tokens[$pos];
|
||||
if ($token->is($expectedTokenType)) {
|
||||
return true;
|
||||
}
|
||||
if ($tokenType !== \T_WHITESPACE
|
||||
&& $tokenType !== \T_COMMENT && $tokenType !== \T_DOC_COMMENT) {
|
||||
if (!$token->isIgnorable()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -87,16 +87,15 @@ class TokenStream
|
||||
*
|
||||
* @return bool Whether the expected token was found
|
||||
*/
|
||||
public function haveTokenImmediatelyAfter(int $pos, $expectedTokenType) : bool {
|
||||
public function haveTokenImmediatelyAfter(int $pos, $expectedTokenType): bool {
|
||||
$tokens = $this->tokens;
|
||||
$pos++;
|
||||
for (; $pos < \count($tokens); $pos++) {
|
||||
$tokenType = $tokens[$pos][0];
|
||||
if ($tokenType === $expectedTokenType) {
|
||||
for ($c = \count($tokens); $pos < $c; $pos++) {
|
||||
$token = $tokens[$pos];
|
||||
if ($token->is($expectedTokenType)) {
|
||||
return true;
|
||||
}
|
||||
if ($tokenType !== \T_WHITESPACE
|
||||
&& $tokenType !== \T_COMMENT && $tokenType !== \T_DOC_COMMENT) {
|
||||
if (!$token->isIgnorable()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -111,7 +110,7 @@ class TokenStream
|
||||
return $pos;
|
||||
}
|
||||
|
||||
if ($tokens[$pos][0] !== $skipTokenType) {
|
||||
if (!$tokens[$pos]->is($skipTokenType)) {
|
||||
// Shouldn't happen. The skip token MUST be there
|
||||
throw new \Exception('Encountered unexpected token');
|
||||
}
|
||||
@ -128,7 +127,7 @@ class TokenStream
|
||||
return $pos;
|
||||
}
|
||||
|
||||
if ($tokens[$pos][0] !== $skipTokenType) {
|
||||
if (!$tokens[$pos]->is($skipTokenType)) {
|
||||
// Shouldn't happen. The skip token MUST be there
|
||||
throw new \Exception('Encountered unexpected token');
|
||||
}
|
||||
@ -143,11 +142,10 @@ class TokenStream
|
||||
* @param int $pos Token position
|
||||
* @return int Non-whitespace token position
|
||||
*/
|
||||
public function skipLeftWhitespace(int $pos) {
|
||||
public function skipLeftWhitespace(int $pos): int {
|
||||
$tokens = $this->tokens;
|
||||
for (; $pos >= 0; $pos--) {
|
||||
$type = $tokens[$pos][0];
|
||||
if ($type !== \T_WHITESPACE && $type !== \T_COMMENT && $type !== \T_DOC_COMMENT) {
|
||||
if (!$tokens[$pos]->isIgnorable()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -160,11 +158,10 @@ class TokenStream
|
||||
* @param int $pos Token position
|
||||
* @return int Non-whitespace token position
|
||||
*/
|
||||
public function skipRightWhitespace(int $pos) {
|
||||
public function skipRightWhitespace(int $pos): int {
|
||||
$tokens = $this->tokens;
|
||||
for ($count = \count($tokens); $pos < $count; $pos++) {
|
||||
$type = $tokens[$pos][0];
|
||||
if ($type !== \T_WHITESPACE && $type !== \T_COMMENT && $type !== \T_DOC_COMMENT) {
|
||||
if (!$tokens[$pos]->isIgnorable()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -174,8 +171,7 @@ class TokenStream
|
||||
public function findRight(int $pos, $findTokenType) {
|
||||
$tokens = $this->tokens;
|
||||
for ($count = \count($tokens); $pos < $count; $pos++) {
|
||||
$type = $tokens[$pos][0];
|
||||
if ($type === $findTokenType) {
|
||||
if ($tokens[$pos]->is($findTokenType)) {
|
||||
return $pos;
|
||||
}
|
||||
}
|
||||
@ -190,10 +186,10 @@ class TokenStream
|
||||
* @param int|string $tokenType Token type to look for
|
||||
* @return bool Whether the token occurs in the given range
|
||||
*/
|
||||
public function haveTokenInRange(int $startPos, int $endPos, $tokenType) {
|
||||
public function haveTokenInRange(int $startPos, int $endPos, $tokenType): bool {
|
||||
$tokens = $this->tokens;
|
||||
for ($pos = $startPos; $pos < $endPos; $pos++) {
|
||||
if ($tokens[$pos][0] === $tokenType) {
|
||||
if ($tokens[$pos]->is($tokenType)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -206,11 +202,6 @@ class TokenStream
|
||||
|| $this->haveTokenInRange($startPos, $endPos, '}');
|
||||
}
|
||||
|
||||
public function haveTagInRange(int $startPos, int $endPos): bool {
|
||||
return $this->haveTokenInRange($startPos, $endPos, \T_OPEN_TAG)
|
||||
|| $this->haveTokenInRange($startPos, $endPos, \T_CLOSE_TAG);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get indentation before token position.
|
||||
*
|
||||
@ -218,7 +209,7 @@ class TokenStream
|
||||
*
|
||||
* @return int Indentation depth (in spaces)
|
||||
*/
|
||||
public function getIndentationBefore(int $pos) : int {
|
||||
public function getIndentationBefore(int $pos): int {
|
||||
return $this->indentMap[$pos];
|
||||
}
|
||||
|
||||
@ -231,28 +222,24 @@ class TokenStream
|
||||
*
|
||||
* @return string Code corresponding to token range, adjusted for indentation
|
||||
*/
|
||||
public function getTokenCode(int $from, int $to, int $indent) : string {
|
||||
public function getTokenCode(int $from, int $to, int $indent): string {
|
||||
$tokens = $this->tokens;
|
||||
$result = '';
|
||||
for ($pos = $from; $pos < $to; $pos++) {
|
||||
$token = $tokens[$pos];
|
||||
if (\is_array($token)) {
|
||||
$type = $token[0];
|
||||
$content = $token[1];
|
||||
if ($type === \T_CONSTANT_ENCAPSED_STRING || $type === \T_ENCAPSED_AND_WHITESPACE) {
|
||||
$result .= $content;
|
||||
} else {
|
||||
// TODO Handle non-space indentation
|
||||
if ($indent < 0) {
|
||||
$result .= str_replace("\n" . str_repeat(" ", -$indent), "\n", $content);
|
||||
} elseif ($indent > 0) {
|
||||
$result .= str_replace("\n", "\n" . str_repeat(" ", $indent), $content);
|
||||
} else {
|
||||
$result .= $content;
|
||||
}
|
||||
}
|
||||
$id = $token->id;
|
||||
$text = $token->text;
|
||||
if ($id === \T_CONSTANT_ENCAPSED_STRING || $id === \T_ENCAPSED_AND_WHITESPACE) {
|
||||
$result .= $text;
|
||||
} else {
|
||||
$result .= $token;
|
||||
// TODO Handle non-space indentation
|
||||
if ($indent < 0) {
|
||||
$result .= str_replace("\n" . str_repeat(" ", -$indent), "\n", $text);
|
||||
} elseif ($indent > 0) {
|
||||
$result .= str_replace("\n", "\n" . str_repeat(" ", $indent), $text);
|
||||
} else {
|
||||
$result .= $text;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
@ -263,14 +250,14 @@ class TokenStream
|
||||
*
|
||||
* @return int[] Token position to indentation map
|
||||
*/
|
||||
private function calcIndentMap() {
|
||||
private function calcIndentMap(): array {
|
||||
$indentMap = [];
|
||||
$indent = 0;
|
||||
foreach ($this->tokens as $token) {
|
||||
$indentMap[] = $indent;
|
||||
|
||||
if ($token[0] === \T_WHITESPACE) {
|
||||
$content = $token[1];
|
||||
if ($token->id === \T_WHITESPACE) {
|
||||
$content = $token->text;
|
||||
$newlinePos = \strrpos($content, "\n");
|
||||
if (false !== $newlinePos) {
|
||||
$indent = \strlen($content) - $newlinePos - 1;
|
||||
|
@ -2,8 +2,7 @@
|
||||
|
||||
namespace PhpParser;
|
||||
|
||||
class JsonDecoder
|
||||
{
|
||||
class JsonDecoder {
|
||||
/** @var \ReflectionClass[] Node type to reflection class map */
|
||||
private $reflectionClassCache;
|
||||
|
||||
@ -29,7 +28,7 @@ class JsonDecoder
|
||||
return $value;
|
||||
}
|
||||
|
||||
private function decodeArray(array $array) : array {
|
||||
private function decodeArray(array $array): array {
|
||||
$decodedArray = [];
|
||||
foreach ($array as $key => $value) {
|
||||
$decodedArray[$key] = $this->decodeRecursive($value);
|
||||
@ -37,7 +36,7 @@ class JsonDecoder
|
||||
return $decodedArray;
|
||||
}
|
||||
|
||||
private function decodeNode(array $value) : Node {
|
||||
private function decodeNode(array $value): Node {
|
||||
$nodeType = $value['nodeType'];
|
||||
if (!\is_string($nodeType)) {
|
||||
throw new \RuntimeException('Node type must be a string');
|
||||
@ -66,7 +65,7 @@ class JsonDecoder
|
||||
return $node;
|
||||
}
|
||||
|
||||
private function decodeComment(array $value) : Comment {
|
||||
private function decodeComment(array $value): Comment {
|
||||
$className = $value['nodeType'] === 'Comment' ? Comment::class : Comment\Doc::class;
|
||||
if (!isset($value['text'])) {
|
||||
throw new \RuntimeException('Comment must have text');
|
||||
@ -79,7 +78,7 @@ class JsonDecoder
|
||||
);
|
||||
}
|
||||
|
||||
private function reflectionClassFromNodeType(string $nodeType) : \ReflectionClass {
|
||||
private function reflectionClassFromNodeType(string $nodeType): \ReflectionClass {
|
||||
if (!isset($this->reflectionClassCache[$nodeType])) {
|
||||
$className = $this->classNameFromNodeType($nodeType);
|
||||
$this->reflectionClassCache[$nodeType] = new \ReflectionClass($className);
|
||||
@ -87,7 +86,7 @@ class JsonDecoder
|
||||
return $this->reflectionClassCache[$nodeType];
|
||||
}
|
||||
|
||||
private function classNameFromNodeType(string $nodeType) : string {
|
||||
private function classNameFromNodeType(string $nodeType): string {
|
||||
$className = 'PhpParser\\Node\\' . strtr($nodeType, '_', '\\');
|
||||
if (class_exists($className)) {
|
||||
return $className;
|
||||
|
@ -4,18 +4,18 @@ namespace PhpParser;
|
||||
|
||||
use PhpParser\Parser\Tokens;
|
||||
|
||||
class Lexer
|
||||
{
|
||||
require __DIR__ . '/compatibility_tokens.php';
|
||||
|
||||
class Lexer {
|
||||
/** @var string Code being tokenized */
|
||||
protected $code;
|
||||
/** @var Token[] Array of tokens */
|
||||
protected $tokens;
|
||||
/** @var int Current position in the token array */
|
||||
protected $pos;
|
||||
protected $line;
|
||||
protected $filePos;
|
||||
protected $prevCloseTagHasNewline;
|
||||
|
||||
protected $tokenMap;
|
||||
protected $dropTokens;
|
||||
protected $identifierTokens;
|
||||
|
||||
private $attributeStartLineUsed;
|
||||
private $attributeEndLineUsed;
|
||||
@ -35,11 +35,6 @@ class Lexer
|
||||
* first three. For more info see getNextToken() docs.
|
||||
*/
|
||||
public function __construct(array $options = []) {
|
||||
// Create Map from internal tokens to PhpParser tokens.
|
||||
$this->defineCompatibilityTokens();
|
||||
$this->tokenMap = $this->createTokenMap();
|
||||
$this->identifierTokens = $this->createIdentifierTokenMap();
|
||||
|
||||
// map of tokens to drop while lexing (the map is only used for isset lookup,
|
||||
// that's why the value is simply set to 1; the value is never actually used.)
|
||||
$this->dropTokens = array_fill_keys(
|
||||
@ -69,15 +64,13 @@ class Lexer
|
||||
* @param ErrorHandler|null $errorHandler Error handler to use for lexing errors. Defaults to
|
||||
* ErrorHandler\Throwing
|
||||
*/
|
||||
public function startLexing(string $code, ErrorHandler $errorHandler = null) {
|
||||
public function startLexing(string $code, ?ErrorHandler $errorHandler = null) {
|
||||
if (null === $errorHandler) {
|
||||
$errorHandler = new ErrorHandler\Throwing();
|
||||
}
|
||||
|
||||
$this->code = $code; // keep the code around for __halt_compiler() handling
|
||||
$this->pos = -1;
|
||||
$this->line = 1;
|
||||
$this->filePos = 0;
|
||||
$this->pos = -1;
|
||||
|
||||
// If inline HTML occurs without preceding code, treat it as if it had a leading newline.
|
||||
// This ensures proper composability, because having a newline is the "safe" assumption.
|
||||
@ -85,7 +78,7 @@ class Lexer
|
||||
|
||||
$scream = ini_set('xdebug.scream', '0');
|
||||
|
||||
$this->tokens = @token_get_all($code);
|
||||
$this->tokens = @Token::tokenize($code);
|
||||
$this->postprocessTokens($errorHandler);
|
||||
|
||||
if (false !== $scream) {
|
||||
@ -93,192 +86,77 @@ class Lexer
|
||||
}
|
||||
}
|
||||
|
||||
private function handleInvalidCharacterRange($start, $end, $line, ErrorHandler $errorHandler) {
|
||||
$tokens = [];
|
||||
for ($i = $start; $i < $end; $i++) {
|
||||
$chr = $this->code[$i];
|
||||
if ($chr === "\0") {
|
||||
// PHP cuts error message after null byte, so need special case
|
||||
$errorMsg = 'Unexpected null byte';
|
||||
} else {
|
||||
$errorMsg = sprintf(
|
||||
'Unexpected character "%s" (ASCII %d)', $chr, ord($chr)
|
||||
);
|
||||
}
|
||||
|
||||
$tokens[] = [\T_BAD_CHARACTER, $chr, $line];
|
||||
$errorHandler->handleError(new Error($errorMsg, [
|
||||
'startLine' => $line,
|
||||
'endLine' => $line,
|
||||
'startFilePos' => $i,
|
||||
'endFilePos' => $i,
|
||||
]));
|
||||
private function handleInvalidCharacter(Token $token, ErrorHandler $errorHandler): void {
|
||||
$chr = $token->text;
|
||||
if ($chr === "\0") {
|
||||
// PHP cuts error message after null byte, so need special case
|
||||
$errorMsg = 'Unexpected null byte';
|
||||
} else {
|
||||
$errorMsg = sprintf(
|
||||
'Unexpected character "%s" (ASCII %d)', $chr, ord($chr)
|
||||
);
|
||||
}
|
||||
return $tokens;
|
||||
|
||||
$errorHandler->handleError(new Error($errorMsg, [
|
||||
'startLine' => $token->line,
|
||||
'endLine' => $token->line,
|
||||
'startFilePos' => $token->pos,
|
||||
'endFilePos' => $token->pos,
|
||||
]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether comment token is unterminated.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function isUnterminatedComment($token) : bool {
|
||||
return ($token[0] === \T_COMMENT || $token[0] === \T_DOC_COMMENT)
|
||||
&& substr($token[1], 0, 2) === '/*'
|
||||
&& substr($token[1], -2) !== '*/';
|
||||
private function isUnterminatedComment(Token $token): bool {
|
||||
return $token->is([\T_COMMENT, \T_DOC_COMMENT])
|
||||
&& substr($token->text, 0, 2) === '/*'
|
||||
&& substr($token->text, -2) !== '*/';
|
||||
}
|
||||
|
||||
protected function postprocessTokens(ErrorHandler $errorHandler) {
|
||||
// PHP's error handling for token_get_all() is rather bad, so if we want detailed
|
||||
// error information we need to compute it ourselves. Invalid character errors are
|
||||
// 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 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.
|
||||
// This function reports errors (bad characters and unterminated comments) in the token
|
||||
// array, and performs certain canonicalizations:
|
||||
// * Use PHP 8.1 T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG and
|
||||
// T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG tokens used to disambiguate intersection types.
|
||||
// * Add a sentinel token with ID 0.
|
||||
|
||||
$filePos = 0;
|
||||
$line = 1;
|
||||
$numTokens = \count($this->tokens);
|
||||
for ($i = 0; $i < $numTokens; $i++) {
|
||||
$token = $this->tokens[$i];
|
||||
|
||||
// Since PHP 7.4 invalid characters are represented by a T_BAD_CHARACTER token.
|
||||
// In this case we only need to emit an error.
|
||||
if ($token[0] === \T_BAD_CHARACTER) {
|
||||
$this->handleInvalidCharacterRange($filePos, $filePos + 1, $line, $errorHandler);
|
||||
}
|
||||
|
||||
if ($token[0] === \T_COMMENT && substr($token[1], 0, 2) !== '/*'
|
||||
&& preg_match('/(\r\n|\n|\r)$/D', $token[1], $matches)) {
|
||||
$trailingNewline = $matches[0];
|
||||
$token[1] = substr($token[1], 0, -strlen($trailingNewline));
|
||||
$this->tokens[$i] = $token;
|
||||
if (isset($this->tokens[$i + 1]) && $this->tokens[$i + 1][0] === \T_WHITESPACE) {
|
||||
// Move trailing newline into following T_WHITESPACE token, if it already exists.
|
||||
$this->tokens[$i + 1][1] = $trailingNewline . $this->tokens[$i + 1][1];
|
||||
$this->tokens[$i + 1][2]--;
|
||||
} else {
|
||||
// Otherwise, we need to create a new T_WHITESPACE token.
|
||||
array_splice($this->tokens, $i + 1, 0, [
|
||||
[\T_WHITESPACE, $trailingNewline, $line],
|
||||
]);
|
||||
$numTokens++;
|
||||
}
|
||||
}
|
||||
|
||||
// Emulate PHP 8 T_NAME_* tokens, by combining sequences of T_NS_SEPARATOR and T_STRING
|
||||
// into a single token.
|
||||
if (\is_array($token)
|
||||
&& ($token[0] === \T_NS_SEPARATOR || isset($this->identifierTokens[$token[0]]))) {
|
||||
$lastWasSeparator = $token[0] === \T_NS_SEPARATOR;
|
||||
$text = $token[1];
|
||||
for ($j = $i + 1; isset($this->tokens[$j]); $j++) {
|
||||
if ($lastWasSeparator) {
|
||||
if (!isset($this->identifierTokens[$this->tokens[$j][0]])) {
|
||||
break;
|
||||
}
|
||||
$lastWasSeparator = false;
|
||||
} else {
|
||||
if ($this->tokens[$j][0] !== \T_NS_SEPARATOR) {
|
||||
break;
|
||||
}
|
||||
$lastWasSeparator = true;
|
||||
}
|
||||
$text .= $this->tokens[$j][1];
|
||||
}
|
||||
if ($lastWasSeparator) {
|
||||
// Trailing separator is not part of the name.
|
||||
$j--;
|
||||
$text = substr($text, 0, -1);
|
||||
}
|
||||
if ($j > $i + 1) {
|
||||
if ($token[0] === \T_NS_SEPARATOR) {
|
||||
$type = \T_NAME_FULLY_QUALIFIED;
|
||||
} else if ($token[0] === \T_NAMESPACE) {
|
||||
$type = \T_NAME_RELATIVE;
|
||||
} else {
|
||||
$type = \T_NAME_QUALIFIED;
|
||||
}
|
||||
$token = [$type, $text, $line];
|
||||
array_splice($this->tokens, $i, $j - $i, [$token]);
|
||||
$numTokens -= $j - $i - 1;
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
if (substr($this->code, $filePos, $tokenLen) !== $tokenValue) {
|
||||
// Something is missing, must be an invalid character
|
||||
$nextFilePos = strpos($this->code, $tokenValue, $filePos);
|
||||
$badCharTokens = $this->handleInvalidCharacterRange(
|
||||
$filePos, $nextFilePos, $line, $errorHandler);
|
||||
$filePos = (int) $nextFilePos;
|
||||
|
||||
array_splice($this->tokens, $i, 0, $badCharTokens);
|
||||
$numTokens += \count($badCharTokens);
|
||||
$i += \count($badCharTokens);
|
||||
}
|
||||
|
||||
$filePos += $tokenLen;
|
||||
$line += substr_count($tokenValue, "\n");
|
||||
}
|
||||
|
||||
if ($filePos !== \strlen($this->code)) {
|
||||
if (substr($this->code, $filePos, 2) === '/*') {
|
||||
// Unlike PHP, HHVM will drop unterminated comments entirely
|
||||
$comment = substr($this->code, $filePos);
|
||||
$errorHandler->handleError(new Error('Unterminated comment', [
|
||||
'startLine' => $line,
|
||||
'endLine' => $line + substr_count($comment, "\n"),
|
||||
'startFilePos' => $filePos,
|
||||
'endFilePos' => $filePos + \strlen($comment),
|
||||
]));
|
||||
|
||||
// Emulate the PHP behavior
|
||||
$isDocComment = isset($comment[3]) && $comment[3] === '*';
|
||||
$this->tokens[] = [$isDocComment ? \T_DOC_COMMENT : \T_COMMENT, $comment, $line];
|
||||
} else {
|
||||
// Invalid characters at the end of the input
|
||||
$badCharTokens = $this->handleInvalidCharacterRange(
|
||||
$filePos, \strlen($this->code), $line, $errorHandler);
|
||||
$this->tokens = array_merge($this->tokens, $badCharTokens);
|
||||
}
|
||||
if ($numTokens === 0) {
|
||||
// Empty input edge case: Just add the sentinel token.
|
||||
$this->tokens[] = new Token(0, "\0", 1, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
if (count($this->tokens) > 0) {
|
||||
// Check for unterminated comment
|
||||
$lastToken = $this->tokens[count($this->tokens) - 1];
|
||||
if ($this->isUnterminatedComment($lastToken)) {
|
||||
$errorHandler->handleError(new Error('Unterminated comment', [
|
||||
'startLine' => $line - substr_count($lastToken[1], "\n"),
|
||||
'endLine' => $line,
|
||||
'startFilePos' => $filePos - \strlen($lastToken[1]),
|
||||
'endFilePos' => $filePos,
|
||||
]));
|
||||
for ($i = 0; $i < $numTokens; $i++) {
|
||||
$token = $this->tokens[$i];
|
||||
if ($token->id === \T_BAD_CHARACTER) {
|
||||
$this->handleInvalidCharacter($token, $errorHandler);
|
||||
}
|
||||
|
||||
if ($token->id === \ord('&')) {
|
||||
$next = $i + 1;
|
||||
while (isset($this->tokens[$next]) && $this->tokens[$next]->id === \T_WHITESPACE) {
|
||||
$next++;
|
||||
}
|
||||
$followedByVarOrVarArg = isset($this->tokens[$next]) &&
|
||||
$this->tokens[$next]->is([\T_VARIABLE, \T_ELLIPSIS]);
|
||||
$token->id = $followedByVarOrVarArg
|
||||
? \T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG
|
||||
: \T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG;
|
||||
}
|
||||
}
|
||||
|
||||
// Check for unterminated comment
|
||||
$lastToken = $this->tokens[$numTokens - 1];
|
||||
if ($this->isUnterminatedComment($lastToken)) {
|
||||
$errorHandler->handleError(new Error('Unterminated comment', [
|
||||
'startLine' => $lastToken->line,
|
||||
'endLine' => $lastToken->getEndLine(),
|
||||
'startFilePos' => $lastToken->pos,
|
||||
'endFilePos' => $lastToken->getEndPos(),
|
||||
]));
|
||||
}
|
||||
|
||||
// Add sentinel token.
|
||||
$this->tokens[] = new Token(0, "\0", $lastToken->getEndLine(), $lastToken->getEndPos());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -303,98 +181,76 @@ class Lexer
|
||||
*
|
||||
* @return int Token id
|
||||
*/
|
||||
public function getNextToken(&$value = null, &$startAttributes = null, &$endAttributes = null) : int {
|
||||
public function getNextToken(&$value = null, &$startAttributes = null, &$endAttributes = null): int {
|
||||
$startAttributes = [];
|
||||
$endAttributes = [];
|
||||
|
||||
while (1) {
|
||||
if (isset($this->tokens[++$this->pos])) {
|
||||
$token = $this->tokens[$this->pos];
|
||||
} else {
|
||||
// EOF token with ID 0
|
||||
$token = "\0";
|
||||
}
|
||||
$token = $this->tokens[++$this->pos];
|
||||
|
||||
if ($this->attributeStartLineUsed) {
|
||||
$startAttributes['startLine'] = $this->line;
|
||||
$startAttributes['startLine'] = $token->line;
|
||||
}
|
||||
if ($this->attributeStartTokenPosUsed) {
|
||||
$startAttributes['startTokenPos'] = $this->pos;
|
||||
}
|
||||
if ($this->attributeStartFilePosUsed) {
|
||||
$startAttributes['startFilePos'] = $this->filePos;
|
||||
$startAttributes['startFilePos'] = $token->pos;
|
||||
}
|
||||
|
||||
if (\is_string($token)) {
|
||||
$value = $token;
|
||||
if (isset($token[1])) {
|
||||
// bug in token_get_all
|
||||
$this->filePos += 2;
|
||||
$id = ord('"');
|
||||
} else {
|
||||
$this->filePos += 1;
|
||||
$id = ord($token);
|
||||
}
|
||||
} elseif (!isset($this->dropTokens[$token[0]])) {
|
||||
$value = $token[1];
|
||||
$id = $this->tokenMap[$token[0]];
|
||||
if (\T_CLOSE_TAG === $token[0]) {
|
||||
$this->prevCloseTagHasNewline = false !== strpos($token[1], "\n")
|
||||
|| false !== strpos($token[1], "\r");
|
||||
} elseif (\T_INLINE_HTML === $token[0]) {
|
||||
$startAttributes['hasLeadingNewline'] = $this->prevCloseTagHasNewline;
|
||||
}
|
||||
|
||||
$this->line += substr_count($value, "\n");
|
||||
$this->filePos += \strlen($value);
|
||||
} else {
|
||||
$origLine = $this->line;
|
||||
$origFilePos = $this->filePos;
|
||||
$this->line += substr_count($token[1], "\n");
|
||||
$this->filePos += \strlen($token[1]);
|
||||
|
||||
if (\T_COMMENT === $token[0] || \T_DOC_COMMENT === $token[0]) {
|
||||
$id = $token->id;
|
||||
if (isset($this->dropTokens[$id])) {
|
||||
if (\T_COMMENT === $id || \T_DOC_COMMENT === $id) {
|
||||
if ($this->attributeCommentsUsed) {
|
||||
$comment = \T_DOC_COMMENT === $token[0]
|
||||
? new Comment\Doc($token[1],
|
||||
$origLine, $origFilePos, $this->pos,
|
||||
$this->line, $this->filePos - 1, $this->pos)
|
||||
: new Comment($token[1],
|
||||
$origLine, $origFilePos, $this->pos,
|
||||
$this->line, $this->filePos - 1, $this->pos);
|
||||
$comment = \T_DOC_COMMENT === $id
|
||||
? new Comment\Doc($token->text, $token->line, $token->pos, $this->pos,
|
||||
$token->getEndLine(), $token->getEndPos() - 1, $this->pos)
|
||||
: new Comment($token->text, $token->line, $token->pos, $this->pos,
|
||||
$token->getEndLine(), $token->getEndPos() - 1, $this->pos);
|
||||
$startAttributes['comments'][] = $comment;
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
$value = $token->text;
|
||||
if (\T_CLOSE_TAG === $token->id) {
|
||||
$this->prevCloseTagHasNewline = false !== strpos($value, "\n")
|
||||
|| false !== strpos($value, "\r");
|
||||
} elseif (\T_INLINE_HTML === $token->id) {
|
||||
$startAttributes['hasLeadingNewline'] = $this->prevCloseTagHasNewline;
|
||||
}
|
||||
|
||||
// Fetch the end line/pos from the next token (if available) instead of recomputing it.
|
||||
$nextToken = $this->tokens[$this->pos + 1] ?? null;
|
||||
if ($this->attributeEndLineUsed) {
|
||||
$endAttributes['endLine'] = $this->line;
|
||||
$endAttributes['endLine'] = $nextToken ? $nextToken->line : $token->getEndLine();
|
||||
}
|
||||
if ($this->attributeEndTokenPosUsed) {
|
||||
$endAttributes['endTokenPos'] = $this->pos;
|
||||
}
|
||||
if ($this->attributeEndFilePosUsed) {
|
||||
$endAttributes['endFilePos'] = $this->filePos - 1;
|
||||
$endAttributes['endFilePos'] = ($nextToken ? $nextToken->pos : $token->getEndPos()) - 1;
|
||||
}
|
||||
|
||||
return $id;
|
||||
}
|
||||
|
||||
throw new \RuntimeException('Reached end of lexer loop');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the token array for current code.
|
||||
*
|
||||
* The token array is in the same format as provided by the
|
||||
* token_get_all() function and does not discard tokens (i.e.
|
||||
* whitespace and comments are included). The token position
|
||||
* attributes are against this token array.
|
||||
* The token array is in the same format as provided by the PhpToken::tokenize() method in
|
||||
* PHP 8.0. The tokens are instances of PhpParser\Token, to abstract over a polyfill
|
||||
* implementation in earlier PHP version.
|
||||
*
|
||||
* @return array Array of tokens in token_get_all() format
|
||||
* The token array is terminated by a sentinel token with token ID 0.
|
||||
* The token array does not discard any tokens (i.e. whitespace and comments are included).
|
||||
* The token position attributes are against this token array.
|
||||
*
|
||||
* @return Token[] Array of tokens
|
||||
*/
|
||||
public function getTokens() : array {
|
||||
public function getTokens(): array {
|
||||
return $this->tokens;
|
||||
}
|
||||
|
||||
@ -403,158 +259,12 @@ class Lexer
|
||||
*
|
||||
* @return string Remaining text
|
||||
*/
|
||||
public function handleHaltCompiler() : string {
|
||||
// text after T_HALT_COMPILER, still including ();
|
||||
$textAfter = substr($this->code, $this->filePos);
|
||||
public function handleHaltCompiler(): string {
|
||||
// Prevent the lexer from returning any further tokens.
|
||||
$nextToken = $this->tokens[$this->pos + 1];
|
||||
$this->pos = \count($this->tokens) - 2;
|
||||
|
||||
// ensure that it is followed by ();
|
||||
// this simplifies the situation, by not allowing any comments
|
||||
// in between of the tokens.
|
||||
if (!preg_match('~^\s*\(\s*\)\s*(?:;|\?>\r?\n?)~', $textAfter, $matches)) {
|
||||
throw new Error('__HALT_COMPILER must be followed by "();"');
|
||||
}
|
||||
|
||||
// prevent the lexer from returning any further tokens
|
||||
$this->pos = count($this->tokens);
|
||||
|
||||
// return with (); removed
|
||||
return substr($textAfter, strlen($matches[0]));
|
||||
}
|
||||
|
||||
private function defineCompatibilityTokens() {
|
||||
static $compatTokensDefined = false;
|
||||
if ($compatTokensDefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
$compatTokens = [
|
||||
// PHP 7.4
|
||||
'T_BAD_CHARACTER',
|
||||
'T_FN',
|
||||
'T_COALESCE_EQUAL',
|
||||
// PHP 8.0
|
||||
'T_NAME_QUALIFIED',
|
||||
'T_NAME_FULLY_QUALIFIED',
|
||||
'T_NAME_RELATIVE',
|
||||
'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
|
||||
// of these tokens. Perform a sanity-check that all already defined tokens have been
|
||||
// assigned a unique ID.
|
||||
$usedTokenIds = [];
|
||||
foreach ($compatTokens as $token) {
|
||||
if (\defined($token)) {
|
||||
$tokenId = \constant($token);
|
||||
$clashingToken = $usedTokenIds[$tokenId] ?? null;
|
||||
if ($clashingToken !== null) {
|
||||
throw new \Error(sprintf(
|
||||
'Token %s has same ID as token %s, ' .
|
||||
'you may be using a library with broken token emulation',
|
||||
$token, $clashingToken
|
||||
));
|
||||
}
|
||||
$usedTokenIds[$tokenId] = $token;
|
||||
}
|
||||
}
|
||||
|
||||
// Now define any tokens that have not yet been emulated. Try to assign IDs from -1
|
||||
// downwards, but skip any IDs that may already be in use.
|
||||
$newTokenId = -1;
|
||||
foreach ($compatTokens as $token) {
|
||||
if (!\defined($token)) {
|
||||
while (isset($usedTokenIds[$newTokenId])) {
|
||||
$newTokenId--;
|
||||
}
|
||||
\define($token, $newTokenId);
|
||||
$newTokenId--;
|
||||
}
|
||||
}
|
||||
|
||||
$compatTokensDefined = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the token map.
|
||||
*
|
||||
* The token map maps the PHP internal token identifiers
|
||||
* to the identifiers used by the Parser. Additionally it
|
||||
* maps T_OPEN_TAG_WITH_ECHO to T_ECHO and T_CLOSE_TAG to ';'.
|
||||
*
|
||||
* @return array The token map
|
||||
*/
|
||||
protected function createTokenMap() : array {
|
||||
$tokenMap = [];
|
||||
|
||||
// 256 is the minimum possible token number, as everything below
|
||||
// it is an ASCII value
|
||||
for ($i = 256; $i < 1000; ++$i) {
|
||||
if (\T_DOUBLE_COLON === $i) {
|
||||
// T_DOUBLE_COLON is equivalent to T_PAAMAYIM_NEKUDOTAYIM
|
||||
$tokenMap[$i] = Tokens::T_PAAMAYIM_NEKUDOTAYIM;
|
||||
} elseif(\T_OPEN_TAG_WITH_ECHO === $i) {
|
||||
// T_OPEN_TAG_WITH_ECHO with dropped T_OPEN_TAG results in T_ECHO
|
||||
$tokenMap[$i] = Tokens::T_ECHO;
|
||||
} elseif(\T_CLOSE_TAG === $i) {
|
||||
// T_CLOSE_TAG is equivalent to ';'
|
||||
$tokenMap[$i] = ord(';');
|
||||
} elseif ('UNKNOWN' !== $name = token_name($i)) {
|
||||
if ('T_HASHBANG' === $name) {
|
||||
// HHVM uses a special token for #! hashbang lines
|
||||
$tokenMap[$i] = Tokens::T_INLINE_HTML;
|
||||
} elseif (defined($name = Tokens::class . '::' . $name)) {
|
||||
// Other tokens can be mapped directly
|
||||
$tokenMap[$i] = constant($name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// HHVM uses a special token for numbers that overflow to double
|
||||
if (defined('T_ONUMBER')) {
|
||||
$tokenMap[\T_ONUMBER] = Tokens::T_DNUMBER;
|
||||
}
|
||||
// HHVM also has a separate token for the __COMPILER_HALT_OFFSET__ constant
|
||||
if (defined('T_COMPILER_HALT_OFFSET')) {
|
||||
$tokenMap[\T_COMPILER_HALT_OFFSET] = Tokens::T_STRING;
|
||||
}
|
||||
|
||||
// Assign tokens for which we define compatibility constants, as token_name() does not know them.
|
||||
$tokenMap[\T_FN] = Tokens::T_FN;
|
||||
$tokenMap[\T_COALESCE_EQUAL] = Tokens::T_COALESCE_EQUAL;
|
||||
$tokenMap[\T_NAME_QUALIFIED] = Tokens::T_NAME_QUALIFIED;
|
||||
$tokenMap[\T_NAME_FULLY_QUALIFIED] = Tokens::T_NAME_FULLY_QUALIFIED;
|
||||
$tokenMap[\T_NAME_RELATIVE] = Tokens::T_NAME_RELATIVE;
|
||||
$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;
|
||||
}
|
||||
|
||||
private function createIdentifierTokenMap(): array {
|
||||
// Based on semi_reserved production.
|
||||
return array_fill_keys([
|
||||
\T_STRING,
|
||||
\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,
|
||||
\T_FINALLY, \T_THROW, \T_USE, \T_INSTEADOF, \T_GLOBAL, \T_VAR, \T_UNSET, \T_ISSET, \T_EMPTY, \T_CONTINUE, \T_GOTO,
|
||||
\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,
|
||||
], true);
|
||||
// Return text after __halt_compiler.
|
||||
return $nextToken->id === \T_INLINE_HTML ? $nextToken->text : '';
|
||||
}
|
||||
}
|
||||
|
@ -14,36 +14,35 @@ use PhpParser\Lexer\TokenEmulator\FnTokenEmulator;
|
||||
use PhpParser\Lexer\TokenEmulator\MatchTokenEmulator;
|
||||
use PhpParser\Lexer\TokenEmulator\NullsafeTokenEmulator;
|
||||
use PhpParser\Lexer\TokenEmulator\NumericLiteralSeparatorEmulator;
|
||||
use PhpParser\Lexer\TokenEmulator\ReadonlyFunctionTokenEmulator;
|
||||
use PhpParser\Lexer\TokenEmulator\ReadonlyTokenEmulator;
|
||||
use PhpParser\Lexer\TokenEmulator\ReverseEmulator;
|
||||
use PhpParser\Lexer\TokenEmulator\TokenEmulator;
|
||||
use PhpParser\PhpVersion;
|
||||
|
||||
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';
|
||||
const PHP_8_2 = '8.2dev';
|
||||
|
||||
class Emulative extends Lexer {
|
||||
/** @var mixed[] Patches used to reverse changes introduced in the code */
|
||||
private $patches = [];
|
||||
|
||||
/** @var TokenEmulator[] */
|
||||
private $emulators = [];
|
||||
|
||||
/** @var string */
|
||||
/** @var PhpVersion */
|
||||
private $targetPhpVersion;
|
||||
/** @var PhpVersion */
|
||||
private $hostPhpVersion;
|
||||
|
||||
/**
|
||||
* @param mixed[] $options Lexer options. In addition to the usual options,
|
||||
* accepts a 'phpVersion' string that specifies the
|
||||
* @param mixed[] $options Lexer options. In addition to the usual options, accepts a
|
||||
* 'phpVersion' (PhpVersion object or string) that specifies the
|
||||
* version to emulate. Defaults to newest supported.
|
||||
*/
|
||||
public function __construct(array $options = [])
|
||||
{
|
||||
$this->targetPhpVersion = $options['phpVersion'] ?? Emulative::PHP_8_2;
|
||||
public function __construct(array $options = []) {
|
||||
$version = $options['phpVersion'] ?? PhpVersion::getNewestSupported();
|
||||
if (!$version instanceof PhpVersion) {
|
||||
$version = PhpVersion::fromString($version);
|
||||
}
|
||||
$this->targetPhpVersion = $version;
|
||||
$this->hostPhpVersion = PhpVersion::getHostVersion();
|
||||
unset($options['phpVersion']);
|
||||
|
||||
parent::__construct($options);
|
||||
@ -59,7 +58,6 @@ class Emulative extends Lexer
|
||||
new EnumTokenEmulator(),
|
||||
new ReadonlyTokenEmulator(),
|
||||
new ExplicitOctalEmulator(),
|
||||
new ReadonlyFunctionTokenEmulator(),
|
||||
];
|
||||
|
||||
// Collect emulators that are relevant for the PHP version we're running
|
||||
@ -68,14 +66,14 @@ class Emulative extends Lexer
|
||||
$emulatorPhpVersion = $emulator->getPhpVersion();
|
||||
if ($this->isForwardEmulationNeeded($emulatorPhpVersion)) {
|
||||
$this->emulators[] = $emulator;
|
||||
} else if ($this->isReverseEmulationNeeded($emulatorPhpVersion)) {
|
||||
} elseif ($this->isReverseEmulationNeeded($emulatorPhpVersion)) {
|
||||
$this->emulators[] = new ReverseEmulator($emulator);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function startLexing(string $code, ErrorHandler $errorHandler = null) {
|
||||
$emulators = array_filter($this->emulators, function($emulator) use($code) {
|
||||
public function startLexing(string $code, ?ErrorHandler $errorHandler = null) {
|
||||
$emulators = array_filter($this->emulators, function ($emulator) use ($code) {
|
||||
return $emulator->isEmulationNeeded($code);
|
||||
});
|
||||
|
||||
@ -108,61 +106,41 @@ class Emulative extends Lexer
|
||||
}
|
||||
}
|
||||
|
||||
private function isForwardEmulationNeeded(string $emulatorPhpVersion): bool {
|
||||
return version_compare(\PHP_VERSION, $emulatorPhpVersion, '<')
|
||||
&& version_compare($this->targetPhpVersion, $emulatorPhpVersion, '>=');
|
||||
private function isForwardEmulationNeeded(PhpVersion $emulatorPhpVersion): bool {
|
||||
return $this->hostPhpVersion->older($emulatorPhpVersion)
|
||||
&& $this->targetPhpVersion->newerOrEqual($emulatorPhpVersion);
|
||||
}
|
||||
|
||||
private function isReverseEmulationNeeded(string $emulatorPhpVersion): bool {
|
||||
return version_compare(\PHP_VERSION, $emulatorPhpVersion, '>=')
|
||||
&& version_compare($this->targetPhpVersion, $emulatorPhpVersion, '<');
|
||||
private function isReverseEmulationNeeded(PhpVersion $emulatorPhpVersion): bool {
|
||||
return $this->hostPhpVersion->newerOrEqual($emulatorPhpVersion)
|
||||
&& $this->targetPhpVersion->older($emulatorPhpVersion);
|
||||
}
|
||||
|
||||
private function sortPatches()
|
||||
{
|
||||
private function sortPatches() {
|
||||
// Patches may be contributed by different emulators.
|
||||
// Make sure they are sorted by increasing patch position.
|
||||
usort($this->patches, function($p1, $p2) {
|
||||
usort($this->patches, function ($p1, $p2) {
|
||||
return $p1[0] <=> $p2[0];
|
||||
});
|
||||
}
|
||||
|
||||
private function fixupTokens()
|
||||
{
|
||||
private function fixupTokens() {
|
||||
if (\count($this->patches) === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Load first patch
|
||||
$patchIdx = 0;
|
||||
|
||||
list($patchPos, $patchType, $patchText) = $this->patches[$patchIdx];
|
||||
|
||||
// We use a manual loop over the tokens, because we modify the array on the fly
|
||||
$pos = 0;
|
||||
$posDelta = 0;
|
||||
for ($i = 0, $c = \count($this->tokens); $i < $c; $i++) {
|
||||
$token = $this->tokens[$i];
|
||||
if (\is_string($token)) {
|
||||
if ($patchPos === $pos) {
|
||||
// Only support replacement for string tokens.
|
||||
assert($patchType === 'replace');
|
||||
$this->tokens[$i] = $patchText;
|
||||
|
||||
// Fetch the next patch
|
||||
$patchIdx++;
|
||||
if ($patchIdx >= \count($this->patches)) {
|
||||
// No more patches, we're done
|
||||
return;
|
||||
}
|
||||
list($patchPos, $patchType, $patchText) = $this->patches[$patchIdx];
|
||||
}
|
||||
|
||||
$pos += \strlen($token);
|
||||
continue;
|
||||
}
|
||||
|
||||
$len = \strlen($token[1]);
|
||||
$posDelta = 0;
|
||||
$pos = $token->pos;
|
||||
$token->pos += $posDelta;
|
||||
$localPosDelta = 0;
|
||||
$len = \strlen($token->text);
|
||||
while ($patchPos >= $pos && $patchPos < $pos + $len) {
|
||||
$patchTextLen = \strlen($patchText);
|
||||
if ($patchType === 'remove') {
|
||||
@ -173,21 +151,21 @@ class Emulative extends Lexer
|
||||
$c--;
|
||||
} else {
|
||||
// Remove from token string
|
||||
$this->tokens[$i][1] = substr_replace(
|
||||
$token[1], '', $patchPos - $pos + $posDelta, $patchTextLen
|
||||
$token->text = substr_replace(
|
||||
$token->text, '', $patchPos - $pos + $localPosDelta, $patchTextLen
|
||||
);
|
||||
$posDelta -= $patchTextLen;
|
||||
$localPosDelta -= $patchTextLen;
|
||||
}
|
||||
} elseif ($patchType === 'add') {
|
||||
// Insert into the token string
|
||||
$this->tokens[$i][1] = substr_replace(
|
||||
$token[1], $patchText, $patchPos - $pos + $posDelta, 0
|
||||
$token->text = substr_replace(
|
||||
$token->text, $patchText, $patchPos - $pos + $localPosDelta, 0
|
||||
);
|
||||
$posDelta += $patchTextLen;
|
||||
} else if ($patchType === 'replace') {
|
||||
$localPosDelta += $patchTextLen;
|
||||
} elseif ($patchType === 'replace') {
|
||||
// Replace inside the token string
|
||||
$this->tokens[$i][1] = substr_replace(
|
||||
$token[1], $patchText, $patchPos - $pos + $posDelta, $patchTextLen
|
||||
$token->text = substr_replace(
|
||||
$token->text, $patchText, $patchPos - $pos + $localPosDelta, $patchTextLen
|
||||
);
|
||||
} else {
|
||||
assert(false);
|
||||
@ -196,22 +174,16 @@ class Emulative extends Lexer
|
||||
// Fetch the next patch
|
||||
$patchIdx++;
|
||||
if ($patchIdx >= \count($this->patches)) {
|
||||
// No more patches, we're done
|
||||
return;
|
||||
// No more patches. However, we still need to adjust position.
|
||||
$patchPos = \PHP_INT_MAX;
|
||||
break;
|
||||
}
|
||||
|
||||
list($patchPos, $patchType, $patchText) = $this->patches[$patchIdx];
|
||||
|
||||
// Multiple patches may apply to the same token. Reload the current one to check
|
||||
// If the new patch applies
|
||||
$token = $this->tokens[$i];
|
||||
}
|
||||
|
||||
$pos += $len;
|
||||
$posDelta += $localPosDelta;
|
||||
}
|
||||
|
||||
// A patch did not apply
|
||||
assert(false);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -235,7 +207,7 @@ class Emulative extends Lexer
|
||||
if ($patchType === 'add') {
|
||||
$posDelta += strlen($patchText);
|
||||
$lineDelta += substr_count($patchText, "\n");
|
||||
} else if ($patchType === 'remove') {
|
||||
} elseif ($patchType === 'remove') {
|
||||
$posDelta -= strlen($patchText);
|
||||
$lineDelta -= substr_count($patchText, "\n");
|
||||
}
|
||||
|
@ -2,43 +2,36 @@
|
||||
|
||||
namespace PhpParser\Lexer\TokenEmulator;
|
||||
|
||||
use PhpParser\Lexer\Emulative;
|
||||
use PhpParser\PhpVersion;
|
||||
use PhpParser\Token;
|
||||
|
||||
final class AttributeEmulator extends TokenEmulator
|
||||
{
|
||||
public function getPhpVersion(): string
|
||||
{
|
||||
return Emulative::PHP_8_0;
|
||||
final class AttributeEmulator extends TokenEmulator {
|
||||
public function getPhpVersion(): PhpVersion {
|
||||
return PhpVersion::fromComponents(8, 0);
|
||||
}
|
||||
|
||||
public function isEmulationNeeded(string $code) : bool
|
||||
{
|
||||
public function isEmulationNeeded(string $code): bool {
|
||||
return strpos($code, '#[') !== false;
|
||||
}
|
||||
|
||||
public function emulate(string $code, array $tokens): array
|
||||
{
|
||||
public function emulate(string $code, array $tokens): array {
|
||||
// We need to manually iterate and manage a count because we'll change
|
||||
// the tokens array on the way.
|
||||
$line = 1;
|
||||
for ($i = 0, $c = count($tokens); $i < $c; ++$i) {
|
||||
if ($tokens[$i] === '#' && isset($tokens[$i + 1]) && $tokens[$i + 1] === '[') {
|
||||
$token = $tokens[$i];
|
||||
if ($token->text === '#' && isset($tokens[$i + 1]) && $tokens[$i + 1]->text === '[') {
|
||||
array_splice($tokens, $i, 2, [
|
||||
[\T_ATTRIBUTE, '#[', $line]
|
||||
new Token(\T_ATTRIBUTE, '#[', $token->line, $token->pos),
|
||||
]);
|
||||
$c--;
|
||||
continue;
|
||||
}
|
||||
if (\is_array($tokens[$i])) {
|
||||
$line += substr_count($tokens[$i][1], "\n");
|
||||
}
|
||||
}
|
||||
|
||||
return $tokens;
|
||||
}
|
||||
|
||||
public function reverseEmulate(string $code, array $tokens): array
|
||||
{
|
||||
public function reverseEmulate(string $code, array $tokens): array {
|
||||
// TODO
|
||||
return $tokens;
|
||||
}
|
||||
|
@ -2,45 +2,38 @@
|
||||
|
||||
namespace PhpParser\Lexer\TokenEmulator;
|
||||
|
||||
use PhpParser\Lexer\Emulative;
|
||||
use PhpParser\PhpVersion;
|
||||
use PhpParser\Token;
|
||||
|
||||
final class CoaleseEqualTokenEmulator extends TokenEmulator
|
||||
{
|
||||
public function getPhpVersion(): string
|
||||
{
|
||||
return Emulative::PHP_7_4;
|
||||
final class CoaleseEqualTokenEmulator extends TokenEmulator {
|
||||
public function getPhpVersion(): PhpVersion {
|
||||
return PhpVersion::fromComponents(7, 4);
|
||||
}
|
||||
|
||||
public function isEmulationNeeded(string $code): bool
|
||||
{
|
||||
public function isEmulationNeeded(string $code): bool {
|
||||
return strpos($code, '??=') !== false;
|
||||
}
|
||||
|
||||
public function emulate(string $code, array $tokens): array
|
||||
{
|
||||
public function emulate(string $code, array $tokens): array {
|
||||
// We need to manually iterate and manage a count because we'll change
|
||||
// the tokens array on the way
|
||||
$line = 1;
|
||||
for ($i = 0, $c = count($tokens); $i < $c; ++$i) {
|
||||
$token = $tokens[$i];
|
||||
if (isset($tokens[$i + 1])) {
|
||||
if ($tokens[$i][0] === T_COALESCE && $tokens[$i + 1] === '=') {
|
||||
if ($token->id === T_COALESCE && $tokens[$i + 1]->text === '=') {
|
||||
array_splice($tokens, $i, 2, [
|
||||
[\T_COALESCE_EQUAL, '??=', $line]
|
||||
new Token(\T_COALESCE_EQUAL, '??=', $token->line, $token->pos),
|
||||
]);
|
||||
$c--;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (\is_array($tokens[$i])) {
|
||||
$line += substr_count($tokens[$i][1], "\n");
|
||||
}
|
||||
}
|
||||
|
||||
return $tokens;
|
||||
}
|
||||
|
||||
public function reverseEmulate(string $code, array $tokens): array
|
||||
{
|
||||
public function reverseEmulate(string $code, array $tokens): array {
|
||||
// ??= was not valid code previously, don't bother.
|
||||
return $tokens;
|
||||
}
|
||||
|
@ -2,30 +2,25 @@
|
||||
|
||||
namespace PhpParser\Lexer\TokenEmulator;
|
||||
|
||||
use PhpParser\Lexer\Emulative;
|
||||
use PhpParser\PhpVersion;
|
||||
|
||||
final class EnumTokenEmulator extends KeywordEmulator
|
||||
{
|
||||
public function getPhpVersion(): string
|
||||
{
|
||||
return Emulative::PHP_8_1;
|
||||
final class EnumTokenEmulator extends KeywordEmulator {
|
||||
public function getPhpVersion(): PhpVersion {
|
||||
return PhpVersion::fromComponents(8, 1);
|
||||
}
|
||||
|
||||
public function getKeywordString(): string
|
||||
{
|
||||
public function getKeywordString(): string {
|
||||
return 'enum';
|
||||
}
|
||||
|
||||
public function getKeywordToken(): int
|
||||
{
|
||||
public function getKeywordToken(): int {
|
||||
return \T_ENUM;
|
||||
}
|
||||
|
||||
protected function isKeywordContext(array $tokens, int $pos): bool
|
||||
{
|
||||
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;
|
||||
&& $tokens[$pos + 1]->id === \T_WHITESPACE
|
||||
&& $tokens[$pos + 2]->id === \T_STRING;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,11 +2,12 @@
|
||||
|
||||
namespace PhpParser\Lexer\TokenEmulator;
|
||||
|
||||
use PhpParser\Lexer\Emulative;
|
||||
use PhpParser\PhpVersion;
|
||||
use PhpParser\Token;
|
||||
|
||||
class ExplicitOctalEmulator extends TokenEmulator {
|
||||
public function getPhpVersion(): string {
|
||||
return Emulative::PHP_8_1;
|
||||
public function getPhpVersion(): PhpVersion {
|
||||
return PhpVersion::fromComponents(8, 1);
|
||||
}
|
||||
|
||||
public function isEmulationNeeded(string $code): bool {
|
||||
@ -15,13 +16,14 @@ class ExplicitOctalEmulator extends TokenEmulator {
|
||||
|
||||
public function emulate(string $code, array $tokens): array {
|
||||
for ($i = 0, $c = count($tokens); $i < $c; ++$i) {
|
||||
if ($tokens[$i][0] == \T_LNUMBER && $tokens[$i][1] === '0' &&
|
||||
isset($tokens[$i + 1]) && $tokens[$i + 1][0] == \T_STRING &&
|
||||
preg_match('/[oO][0-7]+(?:_[0-7]+)*/', $tokens[$i + 1][1])
|
||||
$token = $tokens[$i];
|
||||
if ($token->id == \T_LNUMBER && $token->text === '0' &&
|
||||
isset($tokens[$i + 1]) && $tokens[$i + 1]->id == \T_STRING &&
|
||||
preg_match('/[oO][0-7]+(?:_[0-7]+)*/', $tokens[$i + 1]->text)
|
||||
) {
|
||||
$tokenKind = $this->resolveIntegerOrFloatToken($tokens[$i + 1][1]);
|
||||
$tokenKind = $this->resolveIntegerOrFloatToken($tokens[$i + 1]->text);
|
||||
array_splice($tokens, $i, 2, [
|
||||
[$tokenKind, '0' . $tokens[$i + 1][1], $tokens[$i][2]],
|
||||
new Token($tokenKind, '0' . $tokens[$i + 1]->text, $token->line, $token->pos),
|
||||
]);
|
||||
$c--;
|
||||
}
|
||||
@ -29,8 +31,7 @@ class ExplicitOctalEmulator extends TokenEmulator {
|
||||
return $tokens;
|
||||
}
|
||||
|
||||
private function resolveIntegerOrFloatToken(string $str): int
|
||||
{
|
||||
private function resolveIntegerOrFloatToken(string $str): int {
|
||||
$str = substr($str, 1);
|
||||
$str = str_replace('_', '', $str);
|
||||
$num = octdec($str);
|
||||
@ -41,4 +42,4 @@ class ExplicitOctalEmulator extends TokenEmulator {
|
||||
// Explicit octals were not legal code previously, don't bother.
|
||||
return $tokens;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,34 +2,29 @@
|
||||
|
||||
namespace PhpParser\Lexer\TokenEmulator;
|
||||
|
||||
use PhpParser\Lexer\Emulative;
|
||||
use PhpParser\PhpVersion;
|
||||
|
||||
final class FlexibleDocStringEmulator extends TokenEmulator
|
||||
{
|
||||
const FLEXIBLE_DOC_STRING_REGEX = <<<'REGEX'
|
||||
final class FlexibleDocStringEmulator extends TokenEmulator {
|
||||
private const FLEXIBLE_DOC_STRING_REGEX = <<<'REGEX'
|
||||
/<<<[ \t]*(['"]?)([a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*)\1\r?\n
|
||||
(?:.*\r?\n)*?
|
||||
(?<indentation>\h*)\2(?![a-zA-Z0-9_\x80-\xff])(?<separator>(?:;?[\r\n])?)/x
|
||||
REGEX;
|
||||
|
||||
public function getPhpVersion(): string
|
||||
{
|
||||
return Emulative::PHP_7_3;
|
||||
public function getPhpVersion(): PhpVersion {
|
||||
return PhpVersion::fromComponents(7, 3);
|
||||
}
|
||||
|
||||
public function isEmulationNeeded(string $code) : bool
|
||||
{
|
||||
public function isEmulationNeeded(string $code): bool {
|
||||
return strpos($code, '<<<') !== false;
|
||||
}
|
||||
|
||||
public function emulate(string $code, array $tokens): array
|
||||
{
|
||||
public function emulate(string $code, array $tokens): array {
|
||||
// Handled by preprocessing + fixup.
|
||||
return $tokens;
|
||||
}
|
||||
|
||||
public function reverseEmulate(string $code, array $tokens): array
|
||||
{
|
||||
public function reverseEmulate(string $code, array $tokens): array {
|
||||
// Not supported.
|
||||
return $tokens;
|
||||
}
|
||||
|
@ -2,22 +2,18 @@
|
||||
|
||||
namespace PhpParser\Lexer\TokenEmulator;
|
||||
|
||||
use PhpParser\Lexer\Emulative;
|
||||
use PhpParser\PhpVersion;
|
||||
|
||||
final class FnTokenEmulator extends KeywordEmulator
|
||||
{
|
||||
public function getPhpVersion(): string
|
||||
{
|
||||
return Emulative::PHP_7_4;
|
||||
final class FnTokenEmulator extends KeywordEmulator {
|
||||
public function getPhpVersion(): PhpVersion {
|
||||
return PhpVersion::fromComponents(7, 4);
|
||||
}
|
||||
|
||||
public function getKeywordString(): string
|
||||
{
|
||||
public function getKeywordString(): string {
|
||||
return 'fn';
|
||||
}
|
||||
|
||||
public function getKeywordToken(): int
|
||||
{
|
||||
public function getKeywordToken(): int {
|
||||
return \T_FN;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,43 +2,38 @@
|
||||
|
||||
namespace PhpParser\Lexer\TokenEmulator;
|
||||
|
||||
abstract class KeywordEmulator extends TokenEmulator
|
||||
{
|
||||
abstract function getKeywordString(): string;
|
||||
abstract function getKeywordToken(): int;
|
||||
use PhpParser\Token;
|
||||
|
||||
public function isEmulationNeeded(string $code): bool
|
||||
{
|
||||
abstract class KeywordEmulator extends TokenEmulator {
|
||||
abstract public function getKeywordString(): string;
|
||||
abstract public function getKeywordToken(): int;
|
||||
|
||||
public function isEmulationNeeded(string $code): bool {
|
||||
return strpos(strtolower($code), $this->getKeywordString()) !== false;
|
||||
}
|
||||
|
||||
protected function isKeywordContext(array $tokens, int $pos): bool
|
||||
{
|
||||
/** @param Token[] $tokens */
|
||||
protected function isKeywordContext(array $tokens, int $pos): bool {
|
||||
$previousNonSpaceToken = $this->getPreviousNonSpaceToken($tokens, $pos);
|
||||
return $previousNonSpaceToken === null || $previousNonSpaceToken[0] !== \T_OBJECT_OPERATOR;
|
||||
return $previousNonSpaceToken === null || $previousNonSpaceToken->id !== \T_OBJECT_OPERATOR;
|
||||
}
|
||||
|
||||
public function emulate(string $code, array $tokens): array
|
||||
{
|
||||
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
|
||||
if ($token->id === T_STRING && strtolower($token->text) === $keywordString
|
||||
&& $this->isKeywordContext($tokens, $i)) {
|
||||
$tokens[$i][0] = $this->getKeywordToken();
|
||||
$token->id = $this->getKeywordToken();
|
||||
}
|
||||
}
|
||||
|
||||
return $tokens;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed[] $tokens
|
||||
* @return array|string|null
|
||||
*/
|
||||
private function getPreviousNonSpaceToken(array $tokens, int $start)
|
||||
{
|
||||
/** @param Token[] $tokens */
|
||||
private function getPreviousNonSpaceToken(array $tokens, int $start): ?Token {
|
||||
for ($i = $start - 1; $i >= 0; --$i) {
|
||||
if ($tokens[$i][0] === T_WHITESPACE) {
|
||||
if ($tokens[$i]->id === T_WHITESPACE) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -48,12 +43,11 @@ abstract class KeywordEmulator extends TokenEmulator
|
||||
return null;
|
||||
}
|
||||
|
||||
public function reverseEmulate(string $code, array $tokens): array
|
||||
{
|
||||
public function reverseEmulate(string $code, array $tokens): array {
|
||||
$keywordToken = $this->getKeywordToken();
|
||||
foreach ($tokens as $i => $token) {
|
||||
if ($token[0] === $keywordToken) {
|
||||
$tokens[$i][0] = \T_STRING;
|
||||
if ($token->id === $keywordToken) {
|
||||
$token->id = \T_STRING;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2,22 +2,18 @@
|
||||
|
||||
namespace PhpParser\Lexer\TokenEmulator;
|
||||
|
||||
use PhpParser\Lexer\Emulative;
|
||||
use PhpParser\PhpVersion;
|
||||
|
||||
final class MatchTokenEmulator extends KeywordEmulator
|
||||
{
|
||||
public function getPhpVersion(): string
|
||||
{
|
||||
return Emulative::PHP_8_0;
|
||||
final class MatchTokenEmulator extends KeywordEmulator {
|
||||
public function getPhpVersion(): PhpVersion {
|
||||
return PhpVersion::fromComponents(8, 0);
|
||||
}
|
||||
|
||||
public function getKeywordString(): string
|
||||
{
|
||||
public function getKeywordString(): string {
|
||||
return 'match';
|
||||
}
|
||||
|
||||
public function getKeywordToken(): int
|
||||
{
|
||||
public function getKeywordToken(): int {
|
||||
return \T_MATCH;
|
||||
}
|
||||
}
|
||||
|
@ -2,65 +2,58 @@
|
||||
|
||||
namespace PhpParser\Lexer\TokenEmulator;
|
||||
|
||||
use PhpParser\Lexer\Emulative;
|
||||
use PhpParser\PhpVersion;
|
||||
use PhpParser\Token;
|
||||
|
||||
final class NullsafeTokenEmulator extends TokenEmulator
|
||||
{
|
||||
public function getPhpVersion(): string
|
||||
{
|
||||
return Emulative::PHP_8_0;
|
||||
final class NullsafeTokenEmulator extends TokenEmulator {
|
||||
public function getPhpVersion(): PhpVersion {
|
||||
return PhpVersion::fromComponents(8, 0);
|
||||
}
|
||||
|
||||
public function isEmulationNeeded(string $code): bool
|
||||
{
|
||||
public function isEmulationNeeded(string $code): bool {
|
||||
return strpos($code, '?->') !== false;
|
||||
}
|
||||
|
||||
public function emulate(string $code, array $tokens): array
|
||||
{
|
||||
public function emulate(string $code, array $tokens): array {
|
||||
// We need to manually iterate and manage a count because we'll change
|
||||
// the tokens array on the way
|
||||
$line = 1;
|
||||
for ($i = 0, $c = count($tokens); $i < $c; ++$i) {
|
||||
if ($tokens[$i] === '?' && isset($tokens[$i + 1]) && $tokens[$i + 1][0] === \T_OBJECT_OPERATOR) {
|
||||
$token = $tokens[$i];
|
||||
if ($token->text === '?' && isset($tokens[$i + 1]) && $tokens[$i + 1]->id === \T_OBJECT_OPERATOR) {
|
||||
array_splice($tokens, $i, 2, [
|
||||
[\T_NULLSAFE_OBJECT_OPERATOR, '?->', $line]
|
||||
new Token(\T_NULLSAFE_OBJECT_OPERATOR, '?->', $token->line, $token->pos),
|
||||
]);
|
||||
$c--;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Handle ?-> inside encapsed string.
|
||||
if ($tokens[$i][0] === \T_ENCAPSED_AND_WHITESPACE && isset($tokens[$i - 1])
|
||||
&& $tokens[$i - 1][0] === \T_VARIABLE
|
||||
&& preg_match('/^\?->([a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*)/', $tokens[$i][1], $matches)
|
||||
if ($token->id === \T_ENCAPSED_AND_WHITESPACE && isset($tokens[$i - 1])
|
||||
&& $tokens[$i - 1]->id === \T_VARIABLE
|
||||
&& preg_match('/^\?->([a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*)/', $token->text, $matches)
|
||||
) {
|
||||
$replacement = [
|
||||
[\T_NULLSAFE_OBJECT_OPERATOR, '?->', $line],
|
||||
[\T_STRING, $matches[1], $line],
|
||||
new Token(\T_NULLSAFE_OBJECT_OPERATOR, '?->', $token->line, $token->pos),
|
||||
new Token(\T_STRING, $matches[1], $token->line, $token->pos + 3),
|
||||
];
|
||||
if (\strlen($matches[0]) !== \strlen($tokens[$i][1])) {
|
||||
$replacement[] = [
|
||||
$matchLen = \strlen($matches[0]);
|
||||
if ($matchLen !== \strlen($token->text)) {
|
||||
$replacement[] = new Token(
|
||||
\T_ENCAPSED_AND_WHITESPACE,
|
||||
\substr($tokens[$i][1], \strlen($matches[0])),
|
||||
$line
|
||||
];
|
||||
\substr($token->text, $matchLen),
|
||||
$token->line, $token->pos + $matchLen
|
||||
);
|
||||
}
|
||||
array_splice($tokens, $i, 1, $replacement);
|
||||
$c += \count($replacement) - 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (\is_array($tokens[$i])) {
|
||||
$line += substr_count($tokens[$i][1], "\n");
|
||||
}
|
||||
}
|
||||
|
||||
return $tokens;
|
||||
}
|
||||
|
||||
public function reverseEmulate(string $code, array $tokens): array
|
||||
{
|
||||
public function reverseEmulate(string $code, array $tokens): array {
|
||||
// ?-> was not valid code previously, don't bother.
|
||||
return $tokens;
|
||||
}
|
||||
|
@ -2,70 +2,63 @@
|
||||
|
||||
namespace PhpParser\Lexer\TokenEmulator;
|
||||
|
||||
use PhpParser\Lexer\Emulative;
|
||||
use PhpParser\PhpVersion;
|
||||
use PhpParser\Token;
|
||||
|
||||
final class NumericLiteralSeparatorEmulator extends TokenEmulator
|
||||
{
|
||||
const BIN = '(?:0b[01]+(?:_[01]+)*)';
|
||||
const HEX = '(?:0x[0-9a-f]+(?:_[0-9a-f]+)*)';
|
||||
const DEC = '(?:[0-9]+(?:_[0-9]+)*)';
|
||||
const SIMPLE_FLOAT = '(?:' . self::DEC . '\.' . self::DEC . '?|\.' . self::DEC . ')';
|
||||
const EXP = '(?:e[+-]?' . self::DEC . ')';
|
||||
const FLOAT = '(?:' . self::SIMPLE_FLOAT . self::EXP . '?|' . self::DEC . self::EXP . ')';
|
||||
const NUMBER = '~' . self::FLOAT . '|' . self::BIN . '|' . self::HEX . '|' . self::DEC . '~iA';
|
||||
final class NumericLiteralSeparatorEmulator extends TokenEmulator {
|
||||
private const BIN = '(?:0b[01]+(?:_[01]+)*)';
|
||||
private const HEX = '(?:0x[0-9a-f]+(?:_[0-9a-f]+)*)';
|
||||
private const DEC = '(?:[0-9]+(?:_[0-9]+)*)';
|
||||
private const SIMPLE_FLOAT = '(?:' . self::DEC . '\.' . self::DEC . '?|\.' . self::DEC . ')';
|
||||
private const EXP = '(?:e[+-]?' . self::DEC . ')';
|
||||
private const FLOAT = '(?:' . self::SIMPLE_FLOAT . self::EXP . '?|' . self::DEC . self::EXP . ')';
|
||||
private const NUMBER = '~' . self::FLOAT . '|' . self::BIN . '|' . self::HEX . '|' . self::DEC . '~iA';
|
||||
|
||||
public function getPhpVersion(): string
|
||||
{
|
||||
return Emulative::PHP_7_4;
|
||||
public function getPhpVersion(): PhpVersion {
|
||||
return PhpVersion::fromComponents(7, 4);
|
||||
}
|
||||
|
||||
public function isEmulationNeeded(string $code) : bool
|
||||
{
|
||||
public function isEmulationNeeded(string $code): bool {
|
||||
return preg_match('~[0-9]_[0-9]~', $code)
|
||||
|| preg_match('~0x[0-9a-f]+_[0-9a-f]~i', $code);
|
||||
}
|
||||
|
||||
public function emulate(string $code, array $tokens): array
|
||||
{
|
||||
public function emulate(string $code, array $tokens): array {
|
||||
// We need to manually iterate and manage a count because we'll change
|
||||
// the tokens array on the way
|
||||
$codeOffset = 0;
|
||||
for ($i = 0, $c = count($tokens); $i < $c; ++$i) {
|
||||
$token = $tokens[$i];
|
||||
$tokenLen = \strlen(\is_array($token) ? $token[1] : $token);
|
||||
$tokenLen = \strlen($token->text);
|
||||
|
||||
if ($token[0] !== T_LNUMBER && $token[0] !== T_DNUMBER) {
|
||||
$codeOffset += $tokenLen;
|
||||
if ($token->id !== \T_LNUMBER && $token->id !== \T_DNUMBER) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$res = preg_match(self::NUMBER, $code, $matches, 0, $codeOffset);
|
||||
$res = preg_match(self::NUMBER, $code, $matches, 0, $token->pos);
|
||||
assert($res, "No number at number token position");
|
||||
|
||||
$match = $matches[0];
|
||||
$matchLen = \strlen($match);
|
||||
if ($matchLen === $tokenLen) {
|
||||
// Original token already holds the full number.
|
||||
$codeOffset += $tokenLen;
|
||||
continue;
|
||||
}
|
||||
|
||||
$tokenKind = $this->resolveIntegerOrFloatToken($match);
|
||||
$newTokens = [[$tokenKind, $match, $token[2]]];
|
||||
$newTokens = [new Token($tokenKind, $match, $token->line, $token->pos)];
|
||||
|
||||
$numTokens = 1;
|
||||
$len = $tokenLen;
|
||||
while ($matchLen > $len) {
|
||||
$nextToken = $tokens[$i + $numTokens];
|
||||
$nextTokenText = \is_array($nextToken) ? $nextToken[1] : $nextToken;
|
||||
$nextTokenText = $nextToken->text;
|
||||
$nextTokenLen = \strlen($nextTokenText);
|
||||
|
||||
$numTokens++;
|
||||
if ($matchLen < $len + $nextTokenLen) {
|
||||
// Split trailing characters into a partial token.
|
||||
assert(is_array($nextToken), "Partial token should be an array token");
|
||||
$partialText = substr($nextTokenText, $matchLen - $len);
|
||||
$newTokens[] = [$nextToken[0], $partialText, $nextToken[2]];
|
||||
$newTokens[] = new Token($nextToken->id, $partialText, $nextToken->line, $nextToken->pos);
|
||||
break;
|
||||
}
|
||||
|
||||
@ -74,14 +67,12 @@ final class NumericLiteralSeparatorEmulator extends TokenEmulator
|
||||
|
||||
array_splice($tokens, $i, $numTokens, $newTokens);
|
||||
$c -= $numTokens - \count($newTokens);
|
||||
$codeOffset += $matchLen;
|
||||
}
|
||||
|
||||
return $tokens;
|
||||
}
|
||||
|
||||
private function resolveIntegerOrFloatToken(string $str): int
|
||||
{
|
||||
private function resolveIntegerOrFloatToken(string $str): int {
|
||||
$str = str_replace('_', '', $str);
|
||||
|
||||
if (stripos($str, '0b') === 0) {
|
||||
@ -97,8 +88,7 @@ final class NumericLiteralSeparatorEmulator extends TokenEmulator
|
||||
return is_float($num) ? T_DNUMBER : T_LNUMBER;
|
||||
}
|
||||
|
||||
public function reverseEmulate(string $code, array $tokens): array
|
||||
{
|
||||
public function reverseEmulate(string $code, array $tokens): array {
|
||||
// Numeric separators were not legal code previously, don't bother.
|
||||
return $tokens;
|
||||
}
|
||||
|
@ -1,31 +0,0 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser\Lexer\TokenEmulator;
|
||||
|
||||
use PhpParser\Lexer\Emulative;
|
||||
|
||||
/*
|
||||
* In PHP 8.1, "readonly(" was special cased in the lexer in order to support functions with
|
||||
* name readonly. In PHP 8.2, this may conflict with readonly properties having a DNF type. For
|
||||
* this reason, PHP 8.2 instead treats this as T_READONLY and then handles it specially in the
|
||||
* parser. This emulator only exists to handle this special case, which is skipped by the
|
||||
* PHP 8.1 ReadonlyTokenEmulator.
|
||||
*/
|
||||
class ReadonlyFunctionTokenEmulator extends KeywordEmulator {
|
||||
public function getKeywordString(): string {
|
||||
return 'readonly';
|
||||
}
|
||||
|
||||
public function getKeywordToken(): int {
|
||||
return \T_READONLY;
|
||||
}
|
||||
|
||||
public function getPhpVersion(): string {
|
||||
return Emulative::PHP_8_2;
|
||||
}
|
||||
|
||||
public function reverseEmulate(string $code, array $tokens): array {
|
||||
// Don't bother
|
||||
return $tokens;
|
||||
}
|
||||
}
|
@ -2,35 +2,30 @@
|
||||
|
||||
namespace PhpParser\Lexer\TokenEmulator;
|
||||
|
||||
use PhpParser\Lexer\Emulative;
|
||||
use PhpParser\PhpVersion;
|
||||
|
||||
final class ReadonlyTokenEmulator extends KeywordEmulator
|
||||
{
|
||||
public function getPhpVersion(): string
|
||||
{
|
||||
return Emulative::PHP_8_1;
|
||||
final class ReadonlyTokenEmulator extends KeywordEmulator {
|
||||
public function getPhpVersion(): PhpVersion {
|
||||
return PhpVersion::fromComponents(8, 1);
|
||||
}
|
||||
|
||||
public function getKeywordString(): string
|
||||
{
|
||||
public function getKeywordString(): string {
|
||||
return 'readonly';
|
||||
}
|
||||
|
||||
public function getKeywordToken(): int
|
||||
{
|
||||
public function getKeywordToken(): int {
|
||||
return \T_READONLY;
|
||||
}
|
||||
|
||||
protected function isKeywordContext(array $tokens, int $pos): bool
|
||||
{
|
||||
protected function isKeywordContext(array $tokens, int $pos): bool {
|
||||
if (!parent::isKeywordContext($tokens, $pos)) {
|
||||
return false;
|
||||
}
|
||||
// Support "function readonly("
|
||||
return !(isset($tokens[$pos + 1]) &&
|
||||
($tokens[$pos + 1][0] === '(' ||
|
||||
($tokens[$pos + 1][0] === \T_WHITESPACE &&
|
||||
($tokens[$pos + 1]->text === '(' ||
|
||||
($tokens[$pos + 1]->id === \T_WHITESPACE &&
|
||||
isset($tokens[$pos + 2]) &&
|
||||
$tokens[$pos + 2][0] === '(')));
|
||||
$tokens[$pos + 2]->text === '(')));
|
||||
}
|
||||
}
|
||||
|
@ -2,11 +2,12 @@
|
||||
|
||||
namespace PhpParser\Lexer\TokenEmulator;
|
||||
|
||||
use PhpParser\PhpVersion;
|
||||
|
||||
/**
|
||||
* Reverses emulation direction of the inner emulator.
|
||||
*/
|
||||
final class ReverseEmulator extends TokenEmulator
|
||||
{
|
||||
final class ReverseEmulator extends TokenEmulator {
|
||||
/** @var TokenEmulator Inner emulator */
|
||||
private $emulator;
|
||||
|
||||
@ -14,7 +15,7 @@ final class ReverseEmulator extends TokenEmulator
|
||||
$this->emulator = $emulator;
|
||||
}
|
||||
|
||||
public function getPhpVersion(): string {
|
||||
public function getPhpVersion(): PhpVersion {
|
||||
return $this->emulator->getPhpVersion();
|
||||
}
|
||||
|
||||
@ -33,4 +34,4 @@ final class ReverseEmulator extends TokenEmulator
|
||||
public function preprocessCode(string $code, array &$patches): string {
|
||||
return $code;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,20 +2,23 @@
|
||||
|
||||
namespace PhpParser\Lexer\TokenEmulator;
|
||||
|
||||
use PhpParser\PhpVersion;
|
||||
use PhpParser\Token;
|
||||
|
||||
/** @internal */
|
||||
abstract class TokenEmulator
|
||||
{
|
||||
abstract public function getPhpVersion(): string;
|
||||
abstract class TokenEmulator {
|
||||
abstract public function getPhpVersion(): PhpVersion;
|
||||
|
||||
abstract public function isEmulationNeeded(string $code): bool;
|
||||
|
||||
/**
|
||||
* @return array Modified Tokens
|
||||
* @param Token[] Original tokens
|
||||
* @return Token[] Modified Tokens
|
||||
*/
|
||||
abstract public function emulate(string $code, array $tokens): array;
|
||||
|
||||
/**
|
||||
* @return array Modified Tokens
|
||||
* @return Token[] Modified Tokens
|
||||
*/
|
||||
abstract public function reverseEmulate(string $code, array $tokens): array;
|
||||
|
||||
|
19
lib/PhpParser/Modifiers.php
Normal file
19
lib/PhpParser/Modifiers.php
Normal file
@ -0,0 +1,19 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser;
|
||||
|
||||
/**
|
||||
* Modifiers used (as a bit mask) by various flags subnodes, for example on classes, functions,
|
||||
* properties and constants.
|
||||
*/
|
||||
final class Modifiers {
|
||||
public const PUBLIC = 1;
|
||||
public const PROTECTED = 2;
|
||||
public const PRIVATE = 4;
|
||||
public const STATIC = 8;
|
||||
public const ABSTRACT = 16;
|
||||
public const FINAL = 32;
|
||||
public const READONLY = 64;
|
||||
|
||||
public const VISIBILITY_MASK = 1 | 2 | 4;
|
||||
}
|
@ -6,8 +6,7 @@ use PhpParser\Node\Name;
|
||||
use PhpParser\Node\Name\FullyQualified;
|
||||
use PhpParser\Node\Stmt;
|
||||
|
||||
class NameContext
|
||||
{
|
||||
class NameContext {
|
||||
/** @var null|Name Current namespace */
|
||||
protected $namespace;
|
||||
|
||||
@ -36,7 +35,7 @@ class NameContext
|
||||
*
|
||||
* @param Name|null $namespace Null is the global namespace
|
||||
*/
|
||||
public function startNamespace(Name $namespace = null) {
|
||||
public function startNamespace(?Name $namespace = null) {
|
||||
$this->namespace = $namespace;
|
||||
$this->origAliases = $this->aliases = [
|
||||
Stmt\Use_::TYPE_NORMAL => [],
|
||||
@ -87,7 +86,7 @@ class NameContext
|
||||
*
|
||||
* @return null|Name Namespace (or null if global namespace)
|
||||
*/
|
||||
public function getNamespace() {
|
||||
public function getNamespace(): ?Name {
|
||||
return $this->namespace;
|
||||
}
|
||||
|
||||
@ -99,7 +98,7 @@ class NameContext
|
||||
*
|
||||
* @return null|Name Resolved name, or null if static resolution is not possible
|
||||
*/
|
||||
public function getResolvedName(Name $name, int $type) {
|
||||
public function getResolvedName(Name $name, int $type): ?Name {
|
||||
// don't resolve special class names
|
||||
if ($type === Stmt\Use_::TYPE_NORMAL && $name->isSpecialClassName()) {
|
||||
if (!$name->isUnqualified()) {
|
||||
@ -142,7 +141,7 @@ class NameContext
|
||||
*
|
||||
* @return Name Resolved name
|
||||
*/
|
||||
public function getResolvedClassName(Name $name) : Name {
|
||||
public function getResolvedClassName(Name $name): Name {
|
||||
return $this->getResolvedName($name, Stmt\Use_::TYPE_NORMAL);
|
||||
}
|
||||
|
||||
@ -154,7 +153,7 @@ class NameContext
|
||||
*
|
||||
* @return Name[] Possible representations of the name
|
||||
*/
|
||||
public function getPossibleNames(string $name, int $type) : array {
|
||||
public function getPossibleNames(string $name, int $type): array {
|
||||
$lcName = strtolower($name);
|
||||
|
||||
if ($type === Stmt\Use_::TYPE_NORMAL) {
|
||||
@ -210,7 +209,7 @@ class NameContext
|
||||
*
|
||||
* @return Name Shortest representation
|
||||
*/
|
||||
public function getShortName(string $name, int $type) : Name {
|
||||
public function getShortName(string $name, int $type): Name {
|
||||
$possibleNames = $this->getPossibleNames($name, $type);
|
||||
|
||||
// Find shortest name
|
||||
@ -224,7 +223,7 @@ class NameContext
|
||||
}
|
||||
}
|
||||
|
||||
return $shortestName;
|
||||
return $shortestName;
|
||||
}
|
||||
|
||||
private function resolveAlias(Name $name, $type) {
|
||||
|
@ -2,28 +2,27 @@
|
||||
|
||||
namespace PhpParser;
|
||||
|
||||
interface Node
|
||||
{
|
||||
interface Node {
|
||||
/**
|
||||
* Gets the type of the node.
|
||||
*
|
||||
* @return string Type of the node
|
||||
*/
|
||||
public function getType() : string;
|
||||
public function getType(): string;
|
||||
|
||||
/**
|
||||
* Gets the names of the sub nodes.
|
||||
*
|
||||
* @return array Names of sub nodes
|
||||
*/
|
||||
public function getSubNodeNames() : array;
|
||||
public function getSubNodeNames(): array;
|
||||
|
||||
/**
|
||||
* Gets line the node started in (alias of getStartLine).
|
||||
*
|
||||
* @return int Start line (or -1 if not available)
|
||||
*/
|
||||
public function getLine() : int;
|
||||
public function getLine(): int;
|
||||
|
||||
/**
|
||||
* Gets line the node started in.
|
||||
@ -32,7 +31,7 @@ interface Node
|
||||
*
|
||||
* @return int Start line (or -1 if not available)
|
||||
*/
|
||||
public function getStartLine() : int;
|
||||
public function getStartLine(): int;
|
||||
|
||||
/**
|
||||
* Gets the line the node ended in.
|
||||
@ -41,7 +40,7 @@ interface Node
|
||||
*
|
||||
* @return int End line (or -1 if not available)
|
||||
*/
|
||||
public function getEndLine() : int;
|
||||
public function getEndLine(): int;
|
||||
|
||||
/**
|
||||
* Gets the token offset of the first token that is part of this node.
|
||||
@ -52,7 +51,7 @@ interface Node
|
||||
*
|
||||
* @return int Token start position (or -1 if not available)
|
||||
*/
|
||||
public function getStartTokenPos() : int;
|
||||
public function getStartTokenPos(): int;
|
||||
|
||||
/**
|
||||
* Gets the token offset of the last token that is part of this node.
|
||||
@ -63,7 +62,7 @@ interface Node
|
||||
*
|
||||
* @return int Token end position (or -1 if not available)
|
||||
*/
|
||||
public function getEndTokenPos() : int;
|
||||
public function getEndTokenPos(): int;
|
||||
|
||||
/**
|
||||
* Gets the file offset of the first character that is part of this node.
|
||||
@ -72,7 +71,7 @@ interface Node
|
||||
*
|
||||
* @return int File start position (or -1 if not available)
|
||||
*/
|
||||
public function getStartFilePos() : int;
|
||||
public function getStartFilePos(): int;
|
||||
|
||||
/**
|
||||
* Gets the file offset of the last character that is part of this node.
|
||||
@ -81,7 +80,7 @@ interface Node
|
||||
*
|
||||
* @return int File end position (or -1 if not available)
|
||||
*/
|
||||
public function getEndFilePos() : int;
|
||||
public function getEndFilePos(): int;
|
||||
|
||||
/**
|
||||
* Gets all comments directly preceding this node.
|
||||
@ -90,14 +89,14 @@ interface Node
|
||||
*
|
||||
* @return Comment[]
|
||||
*/
|
||||
public function getComments() : array;
|
||||
public function getComments(): array;
|
||||
|
||||
/**
|
||||
* Gets the doc comment of the node.
|
||||
*
|
||||
* @return null|Comment\Doc Doc comment object or null
|
||||
*/
|
||||
public function getDocComment();
|
||||
public function getDocComment(): ?Comment\Doc;
|
||||
|
||||
/**
|
||||
* Sets the doc comment of the node.
|
||||
@ -123,7 +122,7 @@ interface Node
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasAttribute(string $key) : bool;
|
||||
public function hasAttribute(string $key): bool;
|
||||
|
||||
/**
|
||||
* Returns the value of an attribute.
|
||||
@ -140,7 +139,7 @@ interface Node
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getAttributes() : array;
|
||||
public function getAttributes(): array;
|
||||
|
||||
/**
|
||||
* Replaces all the attributes of this node.
|
||||
|
@ -2,11 +2,9 @@
|
||||
|
||||
namespace PhpParser\Node;
|
||||
|
||||
use PhpParser\Node\VariadicPlaceholder;
|
||||
use PhpParser\NodeAbstract;
|
||||
|
||||
class Arg extends NodeAbstract
|
||||
{
|
||||
class Arg extends NodeAbstract {
|
||||
/** @var Identifier|null Parameter name (for named parameters) */
|
||||
public $name;
|
||||
/** @var Expr Value to pass */
|
||||
@ -27,7 +25,7 @@ class Arg extends NodeAbstract
|
||||
*/
|
||||
public function __construct(
|
||||
Expr $value, bool $byRef = false, bool $unpack = false, array $attributes = [],
|
||||
Identifier $name = null
|
||||
?Identifier $name = null
|
||||
) {
|
||||
$this->attributes = $attributes;
|
||||
$this->name = $name;
|
||||
@ -36,11 +34,11 @@ class Arg extends NodeAbstract
|
||||
$this->unpack = $unpack;
|
||||
}
|
||||
|
||||
public function getSubNodeNames() : array {
|
||||
public function getSubNodeNames(): array {
|
||||
return ['name', 'value', 'byRef', 'unpack'];
|
||||
}
|
||||
|
||||
public function getType() : string {
|
||||
|
||||
public function getType(): string {
|
||||
return 'Arg';
|
||||
}
|
||||
}
|
||||
|
43
lib/PhpParser/Node/ArrayItem.php
Normal file
43
lib/PhpParser/Node/ArrayItem.php
Normal file
@ -0,0 +1,43 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser\Node;
|
||||
|
||||
use PhpParser\NodeAbstract;
|
||||
|
||||
class ArrayItem extends NodeAbstract {
|
||||
/** @var null|Expr Key */
|
||||
public $key;
|
||||
/** @var Expr Value */
|
||||
public $value;
|
||||
/** @var bool Whether to assign by reference */
|
||||
public $byRef;
|
||||
/** @var bool Whether to unpack the argument */
|
||||
public $unpack;
|
||||
|
||||
/**
|
||||
* Constructs an array item node.
|
||||
*
|
||||
* @param Expr $value Value
|
||||
* @param null|Expr $key Key
|
||||
* @param bool $byRef Whether to assign by reference
|
||||
* @param array $attributes Additional attributes
|
||||
*/
|
||||
public function __construct(Expr $value, ?Expr $key = null, bool $byRef = false, array $attributes = [], bool $unpack = false) {
|
||||
$this->attributes = $attributes;
|
||||
$this->key = $key;
|
||||
$this->value = $value;
|
||||
$this->byRef = $byRef;
|
||||
$this->unpack = $unpack;
|
||||
}
|
||||
|
||||
public function getSubNodeNames(): array {
|
||||
return ['key', 'value', 'byRef', 'unpack'];
|
||||
}
|
||||
|
||||
public function getType(): string {
|
||||
return 'ArrayItem';
|
||||
}
|
||||
}
|
||||
|
||||
// @deprecated compatibility alias
|
||||
class_alias(ArrayItem::class, Expr\ArrayItem::class);
|
@ -5,8 +5,7 @@ namespace PhpParser\Node;
|
||||
use PhpParser\Node;
|
||||
use PhpParser\NodeAbstract;
|
||||
|
||||
class Attribute extends NodeAbstract
|
||||
{
|
||||
class Attribute extends NodeAbstract {
|
||||
/** @var Name Attribute name */
|
||||
public $name;
|
||||
|
||||
@ -24,11 +23,11 @@ class Attribute extends NodeAbstract
|
||||
$this->args = $args;
|
||||
}
|
||||
|
||||
public function getSubNodeNames() : array {
|
||||
public function getSubNodeNames(): array {
|
||||
return ['name', 'args'];
|
||||
}
|
||||
|
||||
public function getType() : string {
|
||||
public function getType(): string {
|
||||
return 'Attribute';
|
||||
}
|
||||
}
|
||||
|
@ -2,11 +2,9 @@
|
||||
|
||||
namespace PhpParser\Node;
|
||||
|
||||
use PhpParser\Node;
|
||||
use PhpParser\NodeAbstract;
|
||||
|
||||
class AttributeGroup extends NodeAbstract
|
||||
{
|
||||
class AttributeGroup extends NodeAbstract {
|
||||
/** @var Attribute[] Attributes */
|
||||
public $attrs;
|
||||
|
||||
@ -19,11 +17,11 @@ class AttributeGroup extends NodeAbstract
|
||||
$this->attrs = $attrs;
|
||||
}
|
||||
|
||||
public function getSubNodeNames() : array {
|
||||
public function getSubNodeNames(): array {
|
||||
return ['attrs'];
|
||||
}
|
||||
|
||||
public function getType() : string {
|
||||
public function getType(): string {
|
||||
return 'AttributeGroup';
|
||||
}
|
||||
}
|
||||
|
36
lib/PhpParser/Node/ClosureUse.php
Normal file
36
lib/PhpParser/Node/ClosureUse.php
Normal file
@ -0,0 +1,36 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser\Node;
|
||||
|
||||
use PhpParser\NodeAbstract;
|
||||
|
||||
class ClosureUse extends NodeAbstract {
|
||||
/** @var Expr\Variable Variable to use */
|
||||
public $var;
|
||||
/** @var bool Whether to use by reference */
|
||||
public $byRef;
|
||||
|
||||
/**
|
||||
* Constructs a closure use node.
|
||||
*
|
||||
* @param Expr\Variable $var Variable to use
|
||||
* @param bool $byRef Whether to use by reference
|
||||
* @param array $attributes Additional attributes
|
||||
*/
|
||||
public function __construct(Expr\Variable $var, bool $byRef = false, array $attributes = []) {
|
||||
$this->attributes = $attributes;
|
||||
$this->var = $var;
|
||||
$this->byRef = $byRef;
|
||||
}
|
||||
|
||||
public function getSubNodeNames(): array {
|
||||
return ['var', 'byRef'];
|
||||
}
|
||||
|
||||
public function getType(): string {
|
||||
return 'ClosureUse';
|
||||
}
|
||||
}
|
||||
|
||||
// @deprecated compatibility alias
|
||||
class_alias(ClosureUse::class, Expr\ClosureUse::class);
|
@ -9,6 +9,5 @@ use PhpParser\NodeAbstract;
|
||||
*
|
||||
* It does not provide any shared behavior and exists only for type-checking purposes.
|
||||
*/
|
||||
abstract class ComplexType extends NodeAbstract
|
||||
{
|
||||
abstract class ComplexType extends NodeAbstract {
|
||||
}
|
||||
|
@ -4,8 +4,7 @@ namespace PhpParser\Node;
|
||||
|
||||
use PhpParser\NodeAbstract;
|
||||
|
||||
class Const_ extends NodeAbstract
|
||||
{
|
||||
class Const_ extends NodeAbstract {
|
||||
/** @var Identifier Name */
|
||||
public $name;
|
||||
/** @var Expr Value */
|
||||
@ -27,11 +26,11 @@ class Const_ extends NodeAbstract
|
||||
$this->value = $value;
|
||||
}
|
||||
|
||||
public function getSubNodeNames() : array {
|
||||
public function getSubNodeNames(): array {
|
||||
return ['name', 'value'];
|
||||
}
|
||||
|
||||
public function getType() : string {
|
||||
public function getType(): string {
|
||||
return 'Const';
|
||||
}
|
||||
}
|
||||
|
37
lib/PhpParser/Node/DeclareItem.php
Normal file
37
lib/PhpParser/Node/DeclareItem.php
Normal file
@ -0,0 +1,37 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser\Node;
|
||||
|
||||
use PhpParser\Node;
|
||||
use PhpParser\NodeAbstract;
|
||||
|
||||
class DeclareItem extends NodeAbstract {
|
||||
/** @var Node\Identifier Key */
|
||||
public $key;
|
||||
/** @var Node\Expr Value */
|
||||
public $value;
|
||||
|
||||
/**
|
||||
* Constructs a declare key=>value pair node.
|
||||
*
|
||||
* @param string|Node\Identifier $key Key
|
||||
* @param Node\Expr $value Value
|
||||
* @param array $attributes Additional attributes
|
||||
*/
|
||||
public function __construct($key, Node\Expr $value, array $attributes = []) {
|
||||
$this->attributes = $attributes;
|
||||
$this->key = \is_string($key) ? new Node\Identifier($key) : $key;
|
||||
$this->value = $value;
|
||||
}
|
||||
|
||||
public function getSubNodeNames(): array {
|
||||
return ['key', 'value'];
|
||||
}
|
||||
|
||||
public function getType(): string {
|
||||
return 'DeclareItem';
|
||||
}
|
||||
}
|
||||
|
||||
// @deprecated compatibility alias
|
||||
class_alias(DeclareItem::class, Stmt\DeclareDeclare::class);
|
@ -4,6 +4,5 @@ namespace PhpParser\Node;
|
||||
|
||||
use PhpParser\NodeAbstract;
|
||||
|
||||
abstract class Expr extends NodeAbstract
|
||||
{
|
||||
abstract class Expr extends NodeAbstract {
|
||||
}
|
||||
|
@ -4,8 +4,7 @@ namespace PhpParser\Node\Expr;
|
||||
|
||||
use PhpParser\Node\Expr;
|
||||
|
||||
class ArrayDimFetch extends Expr
|
||||
{
|
||||
class ArrayDimFetch extends Expr {
|
||||
/** @var Expr Variable */
|
||||
public $var;
|
||||
/** @var null|Expr Array index / dim */
|
||||
@ -18,17 +17,17 @@ class ArrayDimFetch extends Expr
|
||||
* @param null|Expr $dim Array index / dim
|
||||
* @param array $attributes Additional attributes
|
||||
*/
|
||||
public function __construct(Expr $var, Expr $dim = null, array $attributes = []) {
|
||||
public function __construct(Expr $var, ?Expr $dim = null, array $attributes = []) {
|
||||
$this->attributes = $attributes;
|
||||
$this->var = $var;
|
||||
$this->dim = $dim;
|
||||
}
|
||||
|
||||
public function getSubNodeNames() : array {
|
||||
public function getSubNodeNames(): array {
|
||||
return ['var', 'dim'];
|
||||
}
|
||||
|
||||
public function getType() : string {
|
||||
|
||||
public function getType(): string {
|
||||
return 'Expr_ArrayDimFetch';
|
||||
}
|
||||
}
|
||||
|
@ -1,41 +1,3 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser\Node\Expr;
|
||||
|
||||
use PhpParser\Node\Expr;
|
||||
|
||||
class ArrayItem extends Expr
|
||||
{
|
||||
/** @var null|Expr Key */
|
||||
public $key;
|
||||
/** @var Expr Value */
|
||||
public $value;
|
||||
/** @var bool Whether to assign by reference */
|
||||
public $byRef;
|
||||
/** @var bool Whether to unpack the argument */
|
||||
public $unpack;
|
||||
|
||||
/**
|
||||
* Constructs an array item node.
|
||||
*
|
||||
* @param Expr $value Value
|
||||
* @param null|Expr $key Key
|
||||
* @param bool $byRef Whether to assign by reference
|
||||
* @param array $attributes Additional attributes
|
||||
*/
|
||||
public function __construct(Expr $value, Expr $key = null, bool $byRef = false, array $attributes = [], bool $unpack = false) {
|
||||
$this->attributes = $attributes;
|
||||
$this->key = $key;
|
||||
$this->value = $value;
|
||||
$this->byRef = $byRef;
|
||||
$this->unpack = $unpack;
|
||||
}
|
||||
|
||||
public function getSubNodeNames() : array {
|
||||
return ['key', 'value', 'byRef', 'unpack'];
|
||||
}
|
||||
|
||||
public function getType() : string {
|
||||
return 'Expr_ArrayItem';
|
||||
}
|
||||
}
|
||||
require __DIR__ . '/../ArrayItem.php';
|
||||
|
@ -2,21 +2,21 @@
|
||||
|
||||
namespace PhpParser\Node\Expr;
|
||||
|
||||
use PhpParser\Node\ArrayItem;
|
||||
use PhpParser\Node\Expr;
|
||||
|
||||
class Array_ extends Expr
|
||||
{
|
||||
class Array_ extends Expr {
|
||||
// For use in "kind" attribute
|
||||
const KIND_LONG = 1; // array() syntax
|
||||
const KIND_SHORT = 2; // [] syntax
|
||||
public const KIND_LONG = 1; // array() syntax
|
||||
public const KIND_SHORT = 2; // [] syntax
|
||||
|
||||
/** @var (ArrayItem|null)[] Items */
|
||||
/** @var ArrayItem[] Items */
|
||||
public $items;
|
||||
|
||||
/**
|
||||
* Constructs an array node.
|
||||
*
|
||||
* @param (ArrayItem|null)[] $items Items of the array
|
||||
* @param ArrayItem[] $items Items of the array
|
||||
* @param array $attributes Additional attributes
|
||||
*/
|
||||
public function __construct(array $items = [], array $attributes = []) {
|
||||
@ -24,11 +24,11 @@ class Array_ extends Expr
|
||||
$this->items = $items;
|
||||
}
|
||||
|
||||
public function getSubNodeNames() : array {
|
||||
public function getSubNodeNames(): array {
|
||||
return ['items'];
|
||||
}
|
||||
|
||||
public function getType() : string {
|
||||
|
||||
public function getType(): string {
|
||||
return 'Expr_Array';
|
||||
}
|
||||
}
|
||||
|
@ -6,8 +6,7 @@ use PhpParser\Node;
|
||||
use PhpParser\Node\Expr;
|
||||
use PhpParser\Node\FunctionLike;
|
||||
|
||||
class ArrowFunction extends Expr implements FunctionLike
|
||||
{
|
||||
class ArrowFunction extends Expr implements FunctionLike {
|
||||
/** @var bool */
|
||||
public $static;
|
||||
|
||||
@ -46,15 +45,15 @@ class ArrowFunction extends Expr implements FunctionLike
|
||||
$this->attrGroups = $subNodes['attrGroups'] ?? [];
|
||||
}
|
||||
|
||||
public function getSubNodeNames() : array {
|
||||
public function getSubNodeNames(): array {
|
||||
return ['attrGroups', 'static', 'byRef', 'params', 'returnType', 'expr'];
|
||||
}
|
||||
|
||||
public function returnsByRef() : bool {
|
||||
public function returnsByRef(): bool {
|
||||
return $this->byRef;
|
||||
}
|
||||
|
||||
public function getParams() : array {
|
||||
public function getParams(): array {
|
||||
return $this->params;
|
||||
}
|
||||
|
||||
@ -62,18 +61,18 @@ class ArrowFunction extends Expr implements FunctionLike
|
||||
return $this->returnType;
|
||||
}
|
||||
|
||||
public function getAttrGroups() : array {
|
||||
public function getAttrGroups(): array {
|
||||
return $this->attrGroups;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Node\Stmt\Return_[]
|
||||
*/
|
||||
public function getStmts() : array {
|
||||
public function getStmts(): array {
|
||||
return [new Node\Stmt\Return_($this->expr)];
|
||||
}
|
||||
|
||||
public function getType() : string {
|
||||
public function getType(): string {
|
||||
return 'Expr_ArrowFunction';
|
||||
}
|
||||
}
|
||||
|
@ -4,8 +4,7 @@ namespace PhpParser\Node\Expr;
|
||||
|
||||
use PhpParser\Node\Expr;
|
||||
|
||||
class Assign extends Expr
|
||||
{
|
||||
class Assign extends Expr {
|
||||
/** @var Expr Variable */
|
||||
public $var;
|
||||
/** @var Expr Expression */
|
||||
@ -24,11 +23,11 @@ class Assign extends Expr
|
||||
$this->expr = $expr;
|
||||
}
|
||||
|
||||
public function getSubNodeNames() : array {
|
||||
public function getSubNodeNames(): array {
|
||||
return ['var', 'expr'];
|
||||
}
|
||||
|
||||
public function getType() : string {
|
||||
|
||||
public function getType(): string {
|
||||
return 'Expr_Assign';
|
||||
}
|
||||
}
|
||||
|
@ -4,8 +4,7 @@ namespace PhpParser\Node\Expr;
|
||||
|
||||
use PhpParser\Node\Expr;
|
||||
|
||||
abstract class AssignOp extends Expr
|
||||
{
|
||||
abstract class AssignOp extends Expr {
|
||||
/** @var Expr Variable */
|
||||
public $var;
|
||||
/** @var Expr Expression */
|
||||
@ -24,7 +23,7 @@ abstract class AssignOp extends Expr
|
||||
$this->expr = $expr;
|
||||
}
|
||||
|
||||
public function getSubNodeNames() : array {
|
||||
public function getSubNodeNames(): array {
|
||||
return ['var', 'expr'];
|
||||
}
|
||||
}
|
||||
|
@ -4,9 +4,8 @@ namespace PhpParser\Node\Expr\AssignOp;
|
||||
|
||||
use PhpParser\Node\Expr\AssignOp;
|
||||
|
||||
class BitwiseAnd extends AssignOp
|
||||
{
|
||||
public function getType() : string {
|
||||
class BitwiseAnd extends AssignOp {
|
||||
public function getType(): string {
|
||||
return 'Expr_AssignOp_BitwiseAnd';
|
||||
}
|
||||
}
|
||||
|
@ -4,9 +4,8 @@ namespace PhpParser\Node\Expr\AssignOp;
|
||||
|
||||
use PhpParser\Node\Expr\AssignOp;
|
||||
|
||||
class BitwiseOr extends AssignOp
|
||||
{
|
||||
public function getType() : string {
|
||||
class BitwiseOr extends AssignOp {
|
||||
public function getType(): string {
|
||||
return 'Expr_AssignOp_BitwiseOr';
|
||||
}
|
||||
}
|
||||
|
@ -4,9 +4,8 @@ namespace PhpParser\Node\Expr\AssignOp;
|
||||
|
||||
use PhpParser\Node\Expr\AssignOp;
|
||||
|
||||
class BitwiseXor extends AssignOp
|
||||
{
|
||||
public function getType() : string {
|
||||
class BitwiseXor extends AssignOp {
|
||||
public function getType(): string {
|
||||
return 'Expr_AssignOp_BitwiseXor';
|
||||
}
|
||||
}
|
||||
|
@ -4,9 +4,8 @@ namespace PhpParser\Node\Expr\AssignOp;
|
||||
|
||||
use PhpParser\Node\Expr\AssignOp;
|
||||
|
||||
class Coalesce extends AssignOp
|
||||
{
|
||||
public function getType() : string {
|
||||
class Coalesce extends AssignOp {
|
||||
public function getType(): string {
|
||||
return 'Expr_AssignOp_Coalesce';
|
||||
}
|
||||
}
|
||||
|
@ -4,9 +4,8 @@ namespace PhpParser\Node\Expr\AssignOp;
|
||||
|
||||
use PhpParser\Node\Expr\AssignOp;
|
||||
|
||||
class Concat extends AssignOp
|
||||
{
|
||||
public function getType() : string {
|
||||
class Concat extends AssignOp {
|
||||
public function getType(): string {
|
||||
return 'Expr_AssignOp_Concat';
|
||||
}
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user