Compare commits

...

60 Commits

Author SHA1 Message Date
5b8182cc0a Release PHP-Parser 3.0.3 2017-02-03 22:57:31 +01:00
af8b17bd75 Update changelog 2017-02-03 22:56:55 +01:00
42f046ec19 Deprecate XML serializer 2017-02-03 22:52:16 +01:00
d287c167bc Pretty print: Handle Error in ClassConstFetch 2017-02-03 21:53:02 +01:00
58e7881e98 Implement NodeTraverser::STOP_TRAVERSAL
Conflicts:
	lib/PhpParser/NodeVisitor.php
2017-01-29 22:35:40 +01:00
fd7ac25108 Throw if pretty-printing Error node 2017-01-29 21:56:21 +01:00
ba57202ed7 Fixed method name casing (#336)
Make it match the parent class.
2017-01-14 12:01:25 +01:00
3e8c8d248d Add originalName attribute in NameResolver
For now gated behind a preserveOriginalNames option.
2016-12-22 20:25:02 +01:00
301c34373d Update run-php-src to use 7.1.0 2016-12-11 16:47:47 +01:00
58970e2a37 Improve LNumber/DNumber pretty printing
* Support PHP_INT_MIN
* Support negative binary/octal/hex numbers
* Support INF/-INF/NAN in namespaces
2016-12-11 16:31:59 +01:00
c1e0bab4f8 Add support for negative interpolated offsets
A late addition to PHP 7.1 which I missed.
2016-12-11 13:44:17 +01:00
d5eebf7214 Add php-parse --with-positions
To invoke NodeDumper in dumpPositions mode
2016-12-09 22:41:46 +01:00
70319e27ee Parse 0 in "$a[0]" as LNumber (#325) 2016-12-07 20:24:00 +01:00
8a97065e30 Add UPGRADE note about NameResolver changes 2016-12-07 20:09:23 +01:00
adf44419c0 Release PHP-Parser 3.0.2 2016-12-06 12:30:35 +01:00
5219f75719 Fix pretty-printing of nullable types 2016-12-06 12:26:21 +01:00
a485ecd7ba NameResolver - resolve Name in NullableType 2016-12-06 12:21:30 +01:00
030de805e1 Add NullableType to types of properties/args that offer it (#323) 2016-12-05 13:30:29 +01:00
aa6aec90e1 Release PHP-Parser 3.0.1 2016-12-01 13:37:30 +01:00
3e158a2313 Wrap List_ in ArrayItem
This was correctly done for the 'key'=>list() form, but not for
unkeyed nested lists.
2016-12-01 13:32:37 +01:00
68973aed1e Release PHP-Parser 3.0 2016-11-30 19:20:29 +01:00
bcdfb703d5 Cleanup imports
Thanks PhpStorm :)
2016-11-23 22:58:18 +01:00
27281e9130 Fix attribute assignment for Error nodes 2016-11-23 22:51:32 +01:00
098294beec Support !!positions parser test mode
And use it for the group use prefix position test that was
previously implemented as a separate test.
2016-11-23 22:36:48 +01:00
b02f8ac07d Add support for dumping positions in NodeDumper 2016-11-23 22:25:17 +01:00
e52ffc4447 Support recovery from Foo:: 2016-11-22 19:47:04 +01:00
c5cdd5ad73 Support recovery from free-standing $ 2016-11-21 17:01:39 +01:00
c0630f8169 Support recovery for new without class name 2016-11-21 16:51:53 +01:00
6db8d9d5a5 Release PHP-Parser 3.0.0-beta2 2016-10-29 13:39:27 +02:00
71438559ae Update docs 2016-10-29 13:37:47 +02:00
c0f0edf044 Mark Name::$parts as deprecated 2016-10-22 17:05:00 +02:00
fa7357b483 Represent empty Name::slice() using null
Instead of a Name([]) dummy value, that is invalid in other
contexts.
2016-10-22 17:02:38 +02:00
91cb82d3d2 Explicitly support Name copy construction
It already worked beforehand by accident ... make clear it's
actually supported.
2016-10-22 16:41:58 +02:00
7672b974ff Remove Name::append() and Name::prepend() 2016-10-22 00:25:15 +02:00
8489364528 Update changelog and upgrading guide 2016-10-21 23:26:51 +02:00
0d0accfa9f Add setDocComment() to Node interface
Previously it was only part of NodeAbstract.
2016-10-21 23:26:51 +02:00
623bad2c8b Type encapsulated string parts specifically 2016-10-21 11:46:54 +02:00
f66cf8f0dd $name can be null for PHP 7 anonymous classes 2016-10-21 11:46:14 +02:00
4e25f51581 Fix php-parse script 2016-10-16 22:19:33 +02:00
a46b309975 Move constants into NodeTraverser class
Not that the interface makes much sense anyway, but these are
implementation details of a specific traverser.
2016-10-16 22:13:09 +02:00
f99a96e0a2 Introduce ErrorHandler
Add ErrorHandler interface, as well as ErrorHandler\Throwing
and ErrorHandler\Collecting. The error handler is passed to
Parser::parse(). This supersedes the throwOnError option.

NameResolver now accepts an ErrorHandler in the ctor.
2016-10-16 22:12:46 +02:00
90834bff8e Add namespacedName attribute on runtime-resolved names
The NameResolver now adds a namespacedName attribute on function/
const names which cannot be statically resolved.
2016-10-11 19:44:22 +02:00
a910f6a247 Remove $separator arg from Name::toString() 2016-10-09 12:45:15 +02:00
caa5c0cc76 Graceful handling for "special" errors
Nearly all special errors are now handled gracefully, i.e. the
parser will be able to continue after encountering them. In some
cases the associated error range has been improved using the new
end attribute stack.

To achieve this the error handling code has been moved out of the
node constructors and into special methods in the parser.
2016-10-09 12:38:18 +02:00
5e5cb86e83 Remove support for node cloning in traverser 2016-10-09 00:41:55 +02:00
2be7838fc6 Correctly assign attrs for encaps vars 2016-10-08 23:58:39 +02:00
f6eb341b15 Fix GroupUse prefix attribute assignment 2016-10-08 23:55:46 +02:00
b2fe43cf7a Next try... 2016-09-30 21:06:48 +02:00
648a246be0 Next try to fix HHVM build 2016-09-30 20:57:21 +02:00
2e5ae28c39 Don't use deprecated getMock() 2016-09-30 20:42:19 +02:00
5025d75160 Try to fix HHVM build 2016-09-30 20:33:56 +02:00
c79ea6d1d3 Support recovery from lexer errors
Lexer::startLexing() no longer throws, instead errors can be fetched
using Lexer::getErrors().

Lexer errors now also contain full line and position information.
2016-09-30 20:23:36 +02:00
e926efd62e Fix regex 2016-09-30 19:17:10 +02:00
17d1e738fa Don't use ~__EMU sequences in emulative lexer
These were necessary back in the day when we had to emulate some
complex functionality such as nowdoc strings. Now we can simply
directly translate certain token sequences.

The motivation for this change is to avoid preprocessing of the source
code, which would complicate offset-aware error handling inside the
lexer as offsets would no longer be correct.
2016-09-30 19:10:16 +02:00
9e5d3bbe25 Remove Error::(get|set)RawLine()
These have been superseded by Error::(get|set)StartLine().
2016-09-30 18:30:01 +02:00
f3c7dc9d89 Add Error::getMessageWithColumnInfo() 2016-09-30 18:24:43 +02:00
c5e0c3d7e2 Catch lexer errors in throwOnError=0 mode 2016-09-30 13:49:34 +02:00
ea47b6e0d6 Add NodeAbstract::setDocComment() 2016-09-17 20:51:22 +02:00
9e1c535b1d Update builder tests to use "flags" instead of "type" 2016-09-17 20:51:22 +02:00
cfd207cae5 Use phpunit 5 if we can rather than 4 (#301) 2016-09-16 17:41:21 +02:00
106 changed files with 3876 additions and 1642 deletions

View File

@ -1,10 +1,102 @@
Version 3.0.0-dev
Version 3.0.4-dev
-----------------
Nothing yet.
Version 3.0.3 (2017-02-03)
--------------------------
### Fixed
* In `"$foo[0]"` the `0` is now parsed as an `LNumber` rather than `String`. (#325)
* Ensure integers and floats are always pretty printed preserving semantics, even if the particular
value can only be manually constructed.
* Throw a `LogicException` when trying to pretty-print an `Error` node. Previously this resulted in
an undefined method exception or fatal error.
### Added
* [PHP 7.1] Added support for negative interpolated offsets: `"$foo[-1]"`
* Added `preserveOriginalNames` option to `NameResolver`. If this option is enabled, an
`originalName` attribute, containing the unresolved name, will be added to each resolved name.
* Added `php-parse --with-positions` option, which dumps nodes with position information.
### Deprecated
* The XML serializer has been deprecated. In particular, the classes `Serializer\XML`,
`Unserializer\XML`, as well as the interfaces `Serializer` and `Unserializer` are deprecated.
Version 3.0.2 (2016-12-06)
--------------------------
### Fixed
* Fixed name resolution of nullable types. (#324)
* Fixed pretty-printing of nullable types.
Version 3.0.1 (2016-12-01)
--------------------------
### Fixed
* Fixed handling of nested `list()`s: If the nested list was unkeyed, it was directly included in
the list items. If it was keyed, it was wrapped in `ArrayItem`. Now nested `List_` nodes are
always wrapped in `ArrayItem`s. (#321)
Version 3.0.0 (2016-11-30)
--------------------------
### Added
* Added support for dumping node positions in the NodeDumper through the `dumpPositions` option.
* Added error recovery support for `$`, `new`, `Foo::`.
Version 3.0.0-beta2 (2016-10-29)
--------------------------------
This release primarily improves our support for error recovery.
### Added
* Added `Node::setDocComment()` method.
* Added `Error::getMessageWithColumnInfo()` method.
* Added support for recovery from lexer errors.
* Added support for recovering from "special" errors (i.e. non-syntax parse errors).
* Added precise location information for lexer errors.
* Added `ErrorHandler` interface, and `ErrorHandler\Throwing` and `ErrorHandler\Collecting` as
specific implementations. These provide a general mechanism for handling error recovery.
* Added optional `ErrorHandler` argument to `Parser::parse()`, `Lexer::startLexing()` and
`NameResolver::__construct()`.
* The `NameResolver` now adds a `namespacedName` attribute on name nodes that cannot be statically
resolved (unqualified unaliased function or constant names in namespaces).
### Fixed
* Fixed attribute assignment for `GroupUse` prefix and variables in interpolated strings.
### Changed
* The constants on `NameTraverserInterface` have been moved into the `NameTraverser` class.
* Due to the error handling changes, the `Parser` interface and `Lexer` API have changed.
* The emulative lexer now directly postprocesses tokens, instead of using `~__EMU__~` sequences.
This changes the protected API of the lexer.
* The `Name::slice()` method now returns `null` for empty slices, previously `new Name([])` was
used. `Name::concat()` now also supports concatenation with `null`.
### Removed
* Removed `Name::append()` and `Name::prepend()`. These mutable methods have been superseded by
the immutable `Name::concat()`.
* Removed `Error::getRawLine()` and `Error::setRawLine()`. These methods have been superseded by
`Error::getStartLine()` and `Error::setStartLine()`.
* Removed support for node cloning in the `NodeTraverser`.
* Removed `$separator` argument from `Name::toString()`.
* Removed `throw_on_error` parser option and `Parser::getErrors()` method. Use the `ErrorHandler`
mechanism instead.
Version 3.0.0-beta1 (2016-09-16)
--------------------------------
### Added
* [7.1] Function/method and parameter builders now support PHP 7.1 type hints (void, iterable and
@ -20,7 +112,7 @@ Version 3.0.0-beta1 (2016-09-16)
The following changes are also part of PHP-Parser 2.1.1:
* The PHP 7 parser will now generate a parse error for `$var =& new Obj` assignments.
* Comments on free-standing code blocks will no be retained as comments on the first statement in
* Comments on free-standing code blocks will now be retained as comments on the first statement in
the code block.
Version 3.0.0-alpha1 (2016-07-25)

View File

@ -6,7 +6,9 @@ PHP Parser
This is a PHP 5.2 to PHP 7.1 parser written in PHP. Its purpose is to simplify static code analysis and
manipulation.
[**Documentation for version 2.x**][doc_master] (stable; for running on PHP >= 5.4; for parsing PHP 5.2 to PHP 7.0).
[**Documentation for version 3.x**][doc_master] (stable; for running on PHP >= 5.5; for parsing PHP 5.2 to PHP 7.1).
[Documentation for version 2.x][doc_2_x] (stable; for running on PHP >= 5.4; for parsing PHP 5.2 to PHP 7.0).
[Documentation for version 1.x][doc_1_x] (unsupported; for running on PHP >= 5.3; for parsing PHP 5.2 to PHP 5.6).
@ -89,7 +91,7 @@ Documentation
Component documentation:
1. [Error](doc/component/Error.markdown)
1. [Error handling](doc/component/Error_handling.markdown)
2. [Lexer](doc/component/Lexer.markdown)
[doc_1_x]: https://github.com/nikic/PHP-Parser/tree/1.x/doc

View File

@ -1,8 +1,13 @@
Upgrading from PHP-Parser 2.x to 3.0
====================================
This version does not include any major API changes. Only specific details of the node
representation have changed in some cases.
The backwards-incompatible changes in this release may be summarized as follows:
* The specific details of the node representation have changed in some cases, primarily to
accomodate new PHP 7.1 features.
* There have been significant changes to the error recovery implementation. This may affect you,
if you used the error recovery mode or have a custom lexer implementation.
* A number of deprecated methods were removed.
### PHP version requirements
@ -31,18 +36,125 @@ The following changes are unlikely to require code changes:
* `void` and `iterable` types are now stored as strings if the PHP 7 parser is used. Previously
these would have been represented as `Name` instances.
### Removed methods
### Changes to error recovery mode
The following methods have been removed:
Previously, error recovery mode was enabled by setting the `throwOnError` option to `false` when
creating the parser, while collected errors were retrieved using the `getErrors()` method:
```php
$lexer = ...;
$parser = (new ParserFactory)->create(ParserFactor::ONLY_PHP7, $lexer, [
'throwOnError' => true,
]);
$stmts = $parser->parse($code);
$errors = $parser->getErrors();
if ($errors) {
handleErrors($errors);
}
processAst($stmts);
```
Both the `throwOnError` option and the `getErrors()` method have been removed in PHP-Parser 3.0.
Instead an instance of `ErrorHandler\Collecting` should be passed to the `parse()` method:
```php
$lexer = ...;
$parser = (new ParserFactory)->create(ParserFactor::ONLY_PHP7, $lexer);
$errorHandler = new ErrorHandler\Collecting;
$stmts = $parser->parse($code, $errorHandler);
if ($errorHandler->hasErrors()) {
handleErrors($errorHandler->getErrors());
}
processAst($stmts);
```
#### Multiple parser fallback in error recovery mode
As a result of this change, if a `Multiple` parser is used (e.g. through the `ParserFactory` using
`PREFER_PHP7` or `PREFER_PHP5`), it will now return the result of the first *non-throwing* parse. As
parsing never throws in error recovery mode, the result from the first parser will always be
returned.
The PHP 7 parser is a superset of the PHP 5 parser, with the exceptions that `=& new` and
`global $$foo->bar` are not supported (other differences are in representation only). The PHP 7
parser will be able to recover from the error in both cases. For this reason, this change will
likely pass unnoticed if you do not specifically test for this syntax.
It is possible to restore the precise previous behavior with the following code:
```php
$lexer = ...;
$parser7 = new Parser\Php7($lexer);
$parser5 = new Parser\Php5($lexer);
$errors7 = new ErrorHandler\Collecting();
$stmts7 = $parser7->parse($code, $errors7);
if ($errors7->hasErrors()) {
$errors5 = new ErrorHandler\Collecting();
$stmts5 = $parser5->parse($code, $errors5);
if (!$errors5->hasErrors()) {
// If PHP 7 parse has errors but PHP 5 parse has no errors, use PHP 5 result
return [$stmts5, $errors5];
}
}
// If PHP 7 succeeds or both fail use PHP 7 result
return [$stmts7, $errors7];
```
#### Error handling in the lexer
In order to support recovery from lexer errors, the signature of the `startLexing()` method changed
to optionally accept an `ErrorHandler`:
```php
// OLD
public function startLexing($code);
// NEW
public function startLexing($code, ErrorHandler $errorHandler = null);
```
If you use a custom lexer with overriden `startLexing()` method, it needs to be changed to accept
the extra parameter. The value should be passed on to the parent method.
#### Error checks in node constructors
The constructors of certain nodes used to contain additional checks for semantic errors, such as
creating a try block without either catch or finally. These checks have been moved from the node
constructors into the parser. This allows recovery from such errors, as well as representing the
resulting (invalid) AST.
This means that certain error conditions are no longer checked for manually constructed nodes.
### Removed methods, arguments, options
The following methods, arguments or options have been removed:
* `Comment::setLine()`, `Comment::setText()`: Create new `Comment` instances instead.
* `Name::set()`, `Name::setFirst()`, `Name::setLast()`: Create new `Name` instances instead. For
the latter two a combination of `Name::concat()` and `Name::slice()` can be used.
* `Name::set()`, `Name::setFirst()`, `Name::setLast()`, `Name::append()`, `Name::prepend()`:
Use `Name::concat()` in combination with `Name::slice()` instead.
* `Error::getRawLine()`, `Error::setRawLine()`. Use `Error::getStartLine()` and
`Error::setStartLine()` instead.
* `Parser::getErrors()`. Use `ErrorHandler\Collecting` instead.
* `$separator` argument of `Name::toString()`. Use `strtr()` instead, if you really need it.
* `$cloneNodes` argument of `NodeTraverser::__construct()`. Explicitly clone nodes in the visitor
instead.
* `throwOnError` parser option. Use `ErrorHandler\Collecting` instead.
### Miscellaneous
* The `NameResolver` will now resolve unqualified function and constant names in the global
namespace into fully qualified names. For example `foo()` in the global namespace resolves to
`\foo()`. For names where no static resolution is possible, a `namespacedName` attribute is
added now, containing the namespaced variant of the name.
* All methods on `PrettyPrinter\Standard` are now protected. Previoulsy most of them were public.
The pretty printer should only be invoked using the `prettyPrint()`, `prettyPrintFile()` and
`prettyPrintExpr()` methods.
* The node dumper now prints numeric values that act as enums/flags in a string representation.
If node dumper results are used in tests, updates may be needed to account for this.
If node dumper results are used in tests, updates may be needed to account for this.
* The constants on `NameTraverserInterface` have been moved into the `NameTraverser` class.
* The emulative lexer now directly postprocesses tokens, instead of using `~__EMU__~` sequences.
This changes the protected API of the emulative lexer.
* The `Name::slice()` method now returns `null` for empty slices, previously `new Name([])` was
used. `Name::concat()` now also supports concatenation with `null`.

View File

@ -31,10 +31,12 @@ $lexer = new PhpParser\Lexer\Emulative(array('usedAttributes' => array(
)));
$parser = (new PhpParser\ParserFactory)->create(
PhpParser\ParserFactory::PREFER_PHP7,
$lexer,
array('throwOnError' => !$attributes['with-recovery'])
$lexer
);
$dumper = new PhpParser\NodeDumper(['dumpComments' => true]);
$dumper = new PhpParser\NodeDumper([
'dumpComments' => true,
'dumpPositions' => $attributes['with-positions'],
]);
$prettyPrinter = new PhpParser\PrettyPrinter\Standard;
$serializer = new PhpParser\Serializer\XML;
@ -54,24 +56,29 @@ foreach ($files as $file) {
echo "====> File $file:\n";
}
try {
$stmts = $parser->parse($code);
foreach ($parser->getErrors() as $error) {
if ($attributes['with-recovery']) {
$errorHandler = new PhpParser\ErrorHandler\Collecting;
$stmts = $parser->parse($code, $errorHandler);
foreach ($errorHandler->getErrors() as $error) {
$message = formatErrorMessage($error, $code, $attributes['with-column-info']);
echo $message . "\n";
}
if (null === $stmts) {
continue;
}
} catch (PhpParser\Error $error) {
$message = formatErrorMessage($error, $code, $attributes['with-column-info']);
die($message . "\n");
} else {
try {
$stmts = $parser->parse($code);
} catch (PhpParser\Error $error) {
$message = formatErrorMessage($error, $code, $attributes['with-column-info']);
die($message . "\n");
}
}
foreach ($operations as $operation) {
if ('dump' === $operation) {
echo "==> Node dump:\n";
echo $dumper->dump($stmts), "\n";
echo $dumper->dump($stmts, $code), "\n";
} elseif ('pretty-print' === $operation) {
echo "==> Pretty print:\n";
echo $prettyPrinter->prettyPrintFile($stmts), "\n";
@ -90,11 +97,7 @@ foreach ($files as $file) {
function formatErrorMessage(PhpParser\Error $e, $code, $withColumnInfo) {
if ($withColumnInfo && $e->hasColumnInfo()) {
$startLine = $e->getStartLine();
$endLine = $e->getEndLine();
$startColumn = $e->getStartColumn($code);
$endColumn = $e->getEndColumn($code);
return $e->getRawMessage() . " from $startLine:$startColumn to $endLine:$endColumn";
return $e->getMessageWithColumnInfo($code);
} else {
return $e->getMessage();
}
@ -117,6 +120,7 @@ Operations is a list of the following options (--dump by default):
--var-dump var_dump() nodes (for exact structure)
-N, --resolve-names Resolve names using NodeVisitor\NameResolver
-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
-h, --help Display this page
@ -134,7 +138,8 @@ function parseArgs($args) {
$operations = array();
$files = array();
$attributes = array(
'with-column-info' => false,
'with-column-info' => false,
'with-posititions' => false,
'with-recovery' => false,
);
@ -169,6 +174,10 @@ function parseArgs($args) {
case '-c';
$attributes['with-column-info'] = true;
break;
case '--with-positions':
case '-P':
$attributes['with-positions'] = true;
break;
case '--with-recovery':
case '-r':
$attributes['with-recovery'] = true;

View File

@ -14,7 +14,7 @@
"ext-tokenizer": "*"
},
"require-dev": {
"phpunit/phpunit": "~4.0"
"phpunit/phpunit": "~4.0|~5.0"
},
"autoload": {
"psr-4": {

View File

@ -8,7 +8,7 @@ Simple serialization
It is possible to serialize the node tree using `serialize()` and also unserialize it using
`unserialize()`. The output is not human readable and not easily processable from anything
but PHP, but it is compact and generates fast. The main application thus is in caching.
but PHP, but it is compact and generates quickly. The main application thus is in caching.
Human readable dumping
----------------------
@ -86,6 +86,134 @@ array(
)
```
JSON encoding
-------------
Nodes (and comments) implement the `JsonSerializable` interface. As such, it is possible to JSON
encode the AST directly using `json_encode()`:
```php
$code = <<<'CODE'
<?php
function printLine($msg) {
echo $msg, "\n";
}
printLine('Hello World!!!');
CODE;
$parser = (new PhpParser\ParserFactory)->create(PhpParser\ParserFactory::PREFER_PHP7);
$nodeDumper = new PhpParser\NodeDumper;
try {
$stmts = $parser->parse($code);
echo json_encode($stmts, JSON_PRETTY_PRINT), "\n";
} catch (PhpParser\Error $e) {
echo 'Parse Error: ', $e->getMessage();
}
```
This will result in the following output (which includes attributes):
```json
[
{
"nodeType": "Stmt_Function",
"byRef": false,
"name": "printLine",
"params": [
{
"nodeType": "Param",
"type": null,
"byRef": false,
"variadic": false,
"name": "msg",
"default": null,
"attributes": {
"startLine": 3,
"endLine": 3
}
}
],
"returnType": null,
"stmts": [
{
"nodeType": "Stmt_Echo",
"exprs": [
{
"nodeType": "Expr_Variable",
"name": "msg",
"attributes": {
"startLine": 4,
"endLine": 4
}
},
{
"nodeType": "Scalar_String",
"value": "\n",
"attributes": {
"startLine": 4,
"endLine": 4,
"kind": 2
}
}
],
"attributes": {
"startLine": 4,
"endLine": 4
}
}
],
"attributes": {
"startLine": 3,
"endLine": 5
}
},
{
"nodeType": "Expr_FuncCall",
"name": {
"nodeType": "Name",
"parts": [
"printLine"
],
"attributes": {
"startLine": 7,
"endLine": 7
}
},
"args": [
{
"nodeType": "Arg",
"value": {
"nodeType": "Scalar_String",
"value": "Hello World!!!",
"attributes": {
"startLine": 7,
"endLine": 7,
"kind": 1
}
},
"byRef": false,
"unpack": false,
"attributes": {
"startLine": 7,
"endLine": 7
}
}
],
"attributes": {
"startLine": 7,
"endLine": 7
}
}
]
```
There is currently no mechanism to convert JSON back into a node tree. Furthermore, not all ASTs
can be JSON encoded. In particular, JSON only supports UTF-8 strings.
Serialization to XML
--------------------

View File

@ -35,6 +35,8 @@ the source code of the parsed file. An example for printing an error:
if ($e->hasColumnInfo()) {
echo $e->getRawMessage() . ' from ' . $e->getStartLine() . ':' . $e->getStartColumn($code)
. ' to ' . $e->getEndLine() . ':' . $e->getEndColumn($code);
// or:
echo $e->getMessageWithColumnInfo();
} else {
echo $e->getMessage();
}
@ -46,27 +48,23 @@ file.
Error recovery
--------------
> **EXPERIMENTAL**
The error behavior of the parser (and other components) is controlled by an `ErrorHandler`. Whenever an error is
encountered, `ErrorHandler::handleError()` is invoked. The default error handling strategy is `ErrorHandler\Throwing`,
which will immediately throw when an error is encountered.
By default the parser will throw an exception upon encountering the first error during parsing. An alternative mode is
also supported, in which the parser will remember the error, but try to continue parsing the rest of the source code.
To enable this mode the `throwOnError` parser option needs to be disabled. Any errors that occurred during parsing can
then be retrieved using `$parser->getErrors()`. The `$parser->parse()` method will either return a partial syntax tree
or `null` if recovery fails.
A usage example:
To instead collect all encountered errors into an array, while trying to continue parsing the rest of the source code,
an instance of `ErrorHandler\Collecting` can be passed to the `Parser::parse()` method. A usage example:
```php
$parser = (new PhpParser\ParserFactory)->create(PhpParser\ParserFactory::PREFER_PHP7, null, array(
'throwOnError' => false,
));
$parser = (new PhpParser\ParserFactory)->create(PhpParser\ParserFactory::ONLY_PHP7);
$errorHandler = new PhpParser\ErrorHandler\Collecting;
$stmts = $parser->parse($code);
$errors = $parser->getErrors();
$stmts = $parser->parse($code, $errorHandler);
foreach ($errors as $error) {
// $error is an ordinary PhpParser\Error
if ($errorHandler->hasErrors()) {
foreach ($errorHandler->getErrors() as $error) {
// $error is an ordinary PhpParser\Error
}
}
if (null !== $stmts) {
@ -74,4 +72,4 @@ if (null !== $stmts) {
}
```
The error recovery implementation is experimental -- it currently won't be able to recover from many types of errors.
The `NameResolver` visitor also accepts an `ErrorHandler` as a constructor argument.

View File

@ -95,13 +95,14 @@ Lexer extension
A lexer has to define the following public interface:
void startLexing(string $code);
void startLexing(string $code, ErrorHandler $errorHandler = null);
array getTokens();
string handleHaltCompiler();
int getNextToken(string &$value = null, array &$startAttributes = null, array &$endAttributes = null);
The `startLexing()` method is invoked with the source code that is to be lexed (including the opening tag) whenever the
`parse()` method of the parser is called. It can be used to reset state or preprocess the source code or tokens.
`parse()` method of the parser is called. It can be used to reset state or preprocess the source code or tokens. The
passes `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.
@ -122,9 +123,10 @@ node and the `$endAttributes` from the last token that is part of the node.
E.g. if the tokens `T_FUNCTION T_STRING ... '{' ... '}'` constitute a node, then the `$startAttributes` from the
`T_FUNCTION` token will be taken and the `$endAttributes` from the `'}'` token.
An application of custom attributes is storing the original formatting of literals: The parser does not retain
information about the formatting of integers (like decimal vs. hexadecimal) or strings (like used quote type or used
escape sequences). This can be remedied by storing the original value in an attribute:
An application of custom attributes is storing the exact original formatting of literals: While the parser does retain
some information about the formatting of integers (like decimal vs. hexadecimal) or strings (like used quote type), it
does not preserve the exact original formatting (e.g. leading zeros for integers or escape sequences in strings). This
can be remedied by storing the original value in an attribute:
```php
use PhpParser\Lexer;
@ -135,9 +137,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_LNUMBER // integer
|| $tokenId == Tokens::T_DNUMBER // floating point number
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
) {
// could also use $startAttributes, doesn't really matter here
$endAttributes['originalValue'] = $value;

View File

@ -55,9 +55,12 @@ top_statement:
| class_declaration_statement { $$ = $1; }
| T_HALT_COMPILER
{ $$ = Stmt\HaltCompiler[$this->lexer->handleHaltCompiler()]; }
| T_NAMESPACE namespace_name ';' { $$ = Stmt\Namespace_[$2, null]; }
| T_NAMESPACE namespace_name '{' top_statement_list '}' { $$ = Stmt\Namespace_[$2, $4]; }
| T_NAMESPACE '{' top_statement_list '}' { $$ = Stmt\Namespace_[null, $3]; }
| T_NAMESPACE namespace_name ';'
{ $$ = Stmt\Namespace_[$2, null]; $this->checkNamespace($$); }
| T_NAMESPACE namespace_name '{' top_statement_list '}'
{ $$ = Stmt\Namespace_[$2, $4]; $this->checkNamespace($$); }
| T_NAMESPACE '{' top_statement_list '}'
{ $$ = Stmt\Namespace_[null, $3]; $this->checkNamespace($$); }
| T_USE use_declarations ';' { $$ = Stmt\Use_[$2, Stmt\Use_::TYPE_NORMAL]; }
| T_USE use_type use_declarations ';' { $$ = Stmt\Use_[$3, $2]; }
| group_use_declaration ';' { $$ = $1; }
@ -72,13 +75,13 @@ use_type:
/* Using namespace_name_parts here to avoid s/r conflict on T_NS_SEPARATOR */
group_use_declaration:
T_USE use_type namespace_name_parts T_NS_SEPARATOR '{' unprefixed_use_declarations '}'
{ $$ = Stmt\GroupUse[Name[$3], $6, $2]; }
{ $$ = Stmt\GroupUse[new Name($3, stackAttributes(#3)), $6, $2]; }
| T_USE use_type T_NS_SEPARATOR namespace_name_parts T_NS_SEPARATOR '{' unprefixed_use_declarations '}'
{ $$ = Stmt\GroupUse[Name[$4], $7, $2]; }
{ $$ = Stmt\GroupUse[new Name($4, stackAttributes(#4)), $7, $2]; }
| T_USE namespace_name_parts T_NS_SEPARATOR '{' inline_use_declarations '}'
{ $$ = Stmt\GroupUse[Name[$2], $5, Stmt\Use_::TYPE_UNKNOWN]; }
{ $$ = Stmt\GroupUse[new Name($2, stackAttributes(#2)), $5, Stmt\Use_::TYPE_UNKNOWN]; }
| T_USE T_NS_SEPARATOR namespace_name_parts T_NS_SEPARATOR '{' inline_use_declarations '}'
{ $$ = Stmt\GroupUse[Name[$3], $6, Stmt\Use_::TYPE_UNKNOWN]; }
{ $$ = Stmt\GroupUse[new Name($3, stackAttributes(#3)), $6, Stmt\Use_::TYPE_UNKNOWN]; }
;
unprefixed_use_declarations:
@ -98,8 +101,10 @@ inline_use_declarations:
;
unprefixed_use_declaration:
namespace_name { $$ = Stmt\UseUse[$1, null, Stmt\Use_::TYPE_UNKNOWN]; }
| namespace_name T_AS T_STRING { $$ = Stmt\UseUse[$1, $3, Stmt\Use_::TYPE_UNKNOWN]; }
namespace_name
{ $$ = Stmt\UseUse[$1, null, Stmt\Use_::TYPE_UNKNOWN]; $this->checkUseUse($$, #1); }
| namespace_name T_AS T_STRING
{ $$ = Stmt\UseUse[$1, $3, Stmt\Use_::TYPE_UNKNOWN]; $this->checkUseUse($$, #3); }
;
use_declaration:
@ -179,7 +184,7 @@ non_empty_statement:
{ $$ = Stmt\Foreach_[$3, $7[0], ['keyVar' => $5, 'byRef' => $7[1], 'stmts' => $9]]; }
| T_DECLARE '(' declare_list ')' declare_statement { $$ = Stmt\Declare_[$3, $5]; }
| T_TRY '{' inner_statement_list '}' catches optional_finally
{ $$ = Stmt\TryCatch[$3, $5, $6]; }
{ $$ = Stmt\TryCatch[$3, $5, $6]; $this->checkTryCatch($$); }
| T_THROW expr ';' { $$ = Stmt\Throw_[$2]; }
| T_GOTO T_STRING ';' { $$ = Stmt\Goto_[$2]; }
| T_STRING ':' { $$ = Stmt\Label[$1]; }
@ -231,9 +236,11 @@ function_declaration_statement:
class_declaration_statement:
class_entry_type T_STRING extends_from implements_list '{' class_statement_list '}'
{ $$ = Stmt\Class_[$2, ['type' => $1, 'extends' => $3, 'implements' => $4, 'stmts' => $6]]; }
{ $$ = Stmt\Class_[$2, ['type' => $1, 'extends' => $3, 'implements' => $4, 'stmts' => $6]];
$this->checkClass($$, #2); }
| T_INTERFACE T_STRING interface_extends_list '{' class_statement_list '}'
{ $$ = Stmt\Interface_[$2, ['extends' => $3, 'stmts' => $5]]; }
{ $$ = Stmt\Interface_[$2, ['extends' => $3, 'stmts' => $5]];
$this->checkInterface($$, #2); }
| T_TRAIT T_STRING '{' class_statement_list '}'
{ $$ = Stmt\Trait_[$2, ['stmts' => $4]]; }
;
@ -362,9 +369,9 @@ non_empty_parameter_list:
parameter:
optional_param_type optional_ref optional_ellipsis T_VARIABLE
{ $$ = Node\Param[parseVar($4), null, $1, $2, $3]; }
{ $$ = Node\Param[parseVar($4), null, $1, $2, $3]; $this->checkParam($$); }
| optional_param_type optional_ref optional_ellipsis T_VARIABLE '=' static_scalar
{ $$ = Node\Param[parseVar($4), $6, $1, $2, $3]; }
{ $$ = Node\Param[parseVar($4), $6, $1, $2, $3]; $this->checkParam($$); }
;
type:
@ -427,10 +434,12 @@ class_statement_list:
;
class_statement:
variable_modifiers property_declaration_list ';' { $$ = Stmt\Property[$1, $2]; }
variable_modifiers property_declaration_list ';'
{ $$ = Stmt\Property[$1, $2]; $this->checkProperty($$, #1); }
| T_CONST class_const_list ';' { $$ = Stmt\ClassConst[$2, 0]; }
| method_modifiers T_FUNCTION optional_ref identifier '(' parameter_list ')' optional_return_type method_body
{ $$ = Stmt\ClassMethod[$4, ['type' => $1, 'byRef' => $3, 'params' => $6, 'returnType' => $8, 'stmts' => $9]]; }
{ $$ = Stmt\ClassMethod[$4, ['type' => $1, 'byRef' => $3, 'params' => $6, 'returnType' => $8, 'stmts' => $9]];
$this->checkClassMethod($$, #1); }
| T_USE name_list trait_adaptations { $$ = Stmt\TraitUse[$2, $3]; }
;
@ -482,7 +491,7 @@ method_modifiers:
non_empty_member_modifiers:
member_modifier { $$ = $1; }
| non_empty_member_modifiers member_modifier { Stmt\Class_::verifyModifier($1, $2); $$ = $1 | $2; }
| non_empty_member_modifiers member_modifier { $this->checkModifier($1, $2, #2); $$ = $1 | $2; }
;
member_modifier:
@ -640,7 +649,8 @@ scalar_dereference:
anonymous_class:
T_CLASS ctor_arguments extends_from implements_list '{' class_statement_list '}'
{ $$ = array(Stmt\Class_[null, ['type' => 0, 'extends' => $3, 'implements' => $4, 'stmts' => $6]], $2); }
{ $$ = array(Stmt\Class_[null, ['type' => 0, 'extends' => $3, 'implements' => $4, 'stmts' => $6]], $2);
$this->checkClass($$[0], -1); }
new_expr:
T_NEW class_name_reference ctor_arguments { $$ = Expr\New_[$2, $3]; }
@ -743,7 +753,7 @@ ctor_arguments:
;
common_scalar:
T_LNUMBER { $$ = Scalar\LNumber::fromString($1, attributes(), true); }
T_LNUMBER { $$ = $this->parseLNumber($1, attributes(), true); }
| T_DNUMBER { $$ = Scalar\DNumber[Scalar\DNumber::parse($1)]; }
| T_CONSTANT_ENCAPSED_STRING
{ $attrs = attributes(); $attrs['kind'] = strKind($1);
@ -929,7 +939,7 @@ list_expr_elements:
list_expr_element:
variable { $$ = Expr\ArrayItem[$1, null, false]; }
| list_expr { $$ = $1; }
| list_expr { $$ = Expr\ArrayItem[$1, null, false]; }
| /* empty */ { $$ = null; }
;
@ -961,10 +971,14 @@ encaps_string_part:
T_ENCAPSED_AND_WHITESPACE { $$ = Scalar\EncapsedStringPart[$1]; }
;
encaps_var:
encaps_base_var:
T_VARIABLE { $$ = Expr\Variable[parseVar($1)]; }
| T_VARIABLE '[' encaps_var_offset ']' { $$ = Expr\ArrayDimFetch[Expr\Variable[parseVar($1)], $3]; }
| T_VARIABLE T_OBJECT_OPERATOR T_STRING { $$ = Expr\PropertyFetch[Expr\Variable[parseVar($1)], $3]; }
;
encaps_var:
encaps_base_var { $$ = $1; }
| encaps_base_var '[' encaps_var_offset ']' { $$ = Expr\ArrayDimFetch[$1, $3]; }
| encaps_base_var T_OBJECT_OPERATOR T_STRING { $$ = Expr\PropertyFetch[$1, $3]; }
| T_DOLLAR_OPEN_CURLY_BRACES expr '}' { $$ = Expr\Variable[$2]; }
| T_DOLLAR_OPEN_CURLY_BRACES T_STRING_VARNAME '}' { $$ = Expr\Variable[$2]; }
| T_DOLLAR_OPEN_CURLY_BRACES T_STRING_VARNAME '[' expr ']' '}'
@ -974,7 +988,7 @@ encaps_var:
encaps_var_offset:
T_STRING { $$ = Scalar\String_[$1]; }
| T_NUM_STRING { $$ = Scalar\String_[$1]; }
| T_NUM_STRING { $$ = $this->parseNumString($1, attributes()); }
| T_VARIABLE { $$ = Expr\Variable[parseVar($1)]; }
;

View File

@ -55,9 +55,12 @@ top_statement:
| class_declaration_statement { $$ = $1; }
| T_HALT_COMPILER
{ $$ = Stmt\HaltCompiler[$this->lexer->handleHaltCompiler()]; }
| T_NAMESPACE namespace_name ';' { $$ = Stmt\Namespace_[$2, null]; }
| T_NAMESPACE namespace_name '{' top_statement_list '}' { $$ = Stmt\Namespace_[$2, $4]; }
| T_NAMESPACE '{' top_statement_list '}' { $$ = Stmt\Namespace_[null, $3]; }
| T_NAMESPACE namespace_name ';'
{ $$ = Stmt\Namespace_[$2, null]; $this->checkNamespace($$); }
| T_NAMESPACE namespace_name '{' top_statement_list '}'
{ $$ = Stmt\Namespace_[$2, $4]; $this->checkNamespace($$); }
| T_NAMESPACE '{' top_statement_list '}'
{ $$ = Stmt\Namespace_[null, $3]; $this->checkNamespace($$); }
| T_USE use_declarations ';' { $$ = Stmt\Use_[$2, Stmt\Use_::TYPE_NORMAL]; }
| T_USE use_type use_declarations ';' { $$ = Stmt\Use_[$3, $2]; }
| group_use_declaration ';' { $$ = $1; }
@ -72,13 +75,13 @@ use_type:
/* Using namespace_name_parts here to avoid s/r conflict on T_NS_SEPARATOR */
group_use_declaration:
T_USE use_type namespace_name_parts T_NS_SEPARATOR '{' unprefixed_use_declarations '}'
{ $$ = Stmt\GroupUse[Name[$3], $6, $2]; }
{ $$ = Stmt\GroupUse[new Name($3, stackAttributes(#3)), $6, $2]; }
| T_USE use_type T_NS_SEPARATOR namespace_name_parts T_NS_SEPARATOR '{' unprefixed_use_declarations '}'
{ $$ = Stmt\GroupUse[Name[$4], $7, $2]; }
{ $$ = Stmt\GroupUse[new Name($4, stackAttributes(#4)), $7, $2]; }
| T_USE namespace_name_parts T_NS_SEPARATOR '{' inline_use_declarations '}'
{ $$ = Stmt\GroupUse[Name[$2], $5, Stmt\Use_::TYPE_UNKNOWN]; }
{ $$ = Stmt\GroupUse[new Name($2, stackAttributes(#2)), $5, Stmt\Use_::TYPE_UNKNOWN]; }
| T_USE T_NS_SEPARATOR namespace_name_parts T_NS_SEPARATOR '{' inline_use_declarations '}'
{ $$ = Stmt\GroupUse[Name[$3], $6, Stmt\Use_::TYPE_UNKNOWN]; }
{ $$ = Stmt\GroupUse[new Name($3, stackAttributes(#3)), $6, Stmt\Use_::TYPE_UNKNOWN]; }
;
unprefixed_use_declarations:
@ -98,8 +101,10 @@ inline_use_declarations:
;
unprefixed_use_declaration:
namespace_name { $$ = Stmt\UseUse[$1, null, Stmt\Use_::TYPE_UNKNOWN]; }
| namespace_name T_AS T_STRING { $$ = Stmt\UseUse[$1, $3, Stmt\Use_::TYPE_UNKNOWN]; }
namespace_name
{ $$ = Stmt\UseUse[$1, null, Stmt\Use_::TYPE_UNKNOWN]; $this->checkUseUse($$, #1); }
| namespace_name T_AS T_STRING
{ $$ = Stmt\UseUse[$1, $3, Stmt\Use_::TYPE_UNKNOWN]; $this->checkUseUse($$, #3); }
;
use_declaration:
@ -175,7 +180,7 @@ non_empty_statement:
{ $$ = Stmt\Foreach_[$3, $7[0], ['keyVar' => $5, 'byRef' => $7[1], 'stmts' => $9]]; }
| T_DECLARE '(' declare_list ')' declare_statement { $$ = Stmt\Declare_[$3, $5]; }
| T_TRY '{' inner_statement_list '}' catches optional_finally
{ $$ = Stmt\TryCatch[$3, $5, $6]; }
{ $$ = Stmt\TryCatch[$3, $5, $6]; $this->checkTryCatch($$); }
| T_THROW expr ';' { $$ = Stmt\Throw_[$2]; }
| T_GOTO T_STRING ';' { $$ = Stmt\Goto_[$2]; }
| T_STRING ':' { $$ = Stmt\Label[$1]; }
@ -232,9 +237,11 @@ function_declaration_statement:
class_declaration_statement:
class_entry_type T_STRING extends_from implements_list '{' class_statement_list '}'
{ $$ = Stmt\Class_[$2, ['type' => $1, 'extends' => $3, 'implements' => $4, 'stmts' => $6]]; }
{ $$ = Stmt\Class_[$2, ['type' => $1, 'extends' => $3, 'implements' => $4, 'stmts' => $6]];
$this->checkClass($$, #2); }
| T_INTERFACE T_STRING interface_extends_list '{' class_statement_list '}'
{ $$ = Stmt\Interface_[$2, ['extends' => $3, 'stmts' => $5]]; }
{ $$ = Stmt\Interface_[$2, ['extends' => $3, 'stmts' => $5]];
$this->checkInterface($$, #2); }
| T_TRAIT T_STRING '{' class_statement_list '}'
{ $$ = Stmt\Trait_[$2, ['stmts' => $4]]; }
;
@ -364,9 +371,9 @@ non_empty_parameter_list:
parameter:
optional_param_type optional_ref optional_ellipsis T_VARIABLE
{ $$ = Node\Param[parseVar($4), null, $1, $2, $3]; }
{ $$ = Node\Param[parseVar($4), null, $1, $2, $3]; $this->checkParam($$); }
| optional_param_type optional_ref optional_ellipsis T_VARIABLE '=' expr
{ $$ = Node\Param[parseVar($4), $6, $1, $2, $3]; }
{ $$ = Node\Param[parseVar($4), $6, $1, $2, $3]; $this->checkParam($$); }
;
type_expr:
@ -431,10 +438,13 @@ class_statement_list:
;
class_statement:
variable_modifiers property_declaration_list ';' { $$ = Stmt\Property[$1, $2]; }
| method_modifiers T_CONST class_const_list ';' { $$ = Stmt\ClassConst[$3, $1]; }
variable_modifiers property_declaration_list ';'
{ $$ = Stmt\Property[$1, $2]; $this->checkProperty($$, #1); }
| method_modifiers T_CONST class_const_list ';'
{ $$ = Stmt\ClassConst[$3, $1]; $this->checkClassConst($$, #1); }
| method_modifiers T_FUNCTION optional_ref identifier '(' parameter_list ')' optional_return_type method_body
{ $$ = Stmt\ClassMethod[$4, ['type' => $1, 'byRef' => $3, 'params' => $6, 'returnType' => $8, 'stmts' => $9]]; }
{ $$ = Stmt\ClassMethod[$4, ['type' => $1, 'byRef' => $3, 'params' => $6, 'returnType' => $8, 'stmts' => $9]];
$this->checkClassMethod($$, #1); }
| T_USE name_list trait_adaptations { $$ = Stmt\TraitUse[$2, $3]; }
;
@ -486,7 +496,7 @@ method_modifiers:
non_empty_member_modifiers:
member_modifier { $$ = $1; }
| non_empty_member_modifiers member_modifier { Stmt\Class_::verifyModifier($1, $2); $$ = $1 | $2; }
| non_empty_member_modifiers member_modifier { $this->checkModifier($1, $2, #2); $$ = $1 | $2; }
;
member_modifier:
@ -613,7 +623,8 @@ expr:
anonymous_class:
T_CLASS ctor_arguments extends_from implements_list '{' class_statement_list '}'
{ $$ = array(Stmt\Class_[null, ['type' => 0, 'extends' => $3, 'implements' => $4, 'stmts' => $6]], $2); }
{ $$ = array(Stmt\Class_[null, ['type' => 0, 'extends' => $3, 'implements' => $4, 'stmts' => $6]], $2);
$this->checkClass($$[0], -1); }
new_expr:
T_NEW class_name_reference ctor_arguments { $$ = Expr\New_[$2, $3]; }
@ -656,6 +667,7 @@ name:
class_name_reference:
class_name { $$ = $1; }
| new_variable { $$ = $1; }
| error { $$ = Expr\Error[]; $this->errorState = 2; }
;
class_name_or_var:
@ -684,6 +696,10 @@ constant:
name { $$ = Expr\ConstFetch[$1]; }
| class_name_or_var T_PAAMAYIM_NEKUDOTAYIM identifier
{ $$ = Expr\ClassConstFetch[$1, $3]; }
/* We interpret and 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
{ $$ = Expr\ClassConstFetch[$1, Expr\Error[]]; $this->errorState = 2; }
;
array_short_syntax:
@ -703,7 +719,7 @@ dereferencable_scalar:
;
scalar:
T_LNUMBER { $$ = Scalar\LNumber::fromString($1, attributes()); }
T_LNUMBER { $$ = $this->parseLNumber($1, attributes()); }
| T_DNUMBER { $$ = Scalar\DNumber[Scalar\DNumber::parse($1)]; }
| T_LINE { $$ = Scalar\MagicConst\Line[]; }
| T_FILE { $$ = Scalar\MagicConst\File[]; }
@ -766,6 +782,7 @@ simple_variable:
T_VARIABLE { $$ = parseVar($1); }
| '$' '{' expr '}' { $$ = $3; }
| '$' simple_variable { $$ = Expr\Variable[$2]; }
| '$' error { $$ = Expr\Error[]; $this->errorState = 2; }
;
static_member:
@ -806,7 +823,7 @@ list_expr_elements:
list_expr_element:
variable { $$ = Expr\ArrayItem[$1, null, false]; }
| list_expr { $$ = $1; }
| list_expr { $$ = Expr\ArrayItem[$1, null, false]; }
| expr T_DOUBLE_ARROW variable { $$ = Expr\ArrayItem[$3, $1, false]; }
| expr T_DOUBLE_ARROW list_expr { $$ = Expr\ArrayItem[$3, $1, false]; }
| /* empty */ { $$ = null; }
@ -841,10 +858,14 @@ encaps_string_part:
T_ENCAPSED_AND_WHITESPACE { $$ = Scalar\EncapsedStringPart[$1]; }
;
encaps_var:
encaps_base_var:
T_VARIABLE { $$ = Expr\Variable[parseVar($1)]; }
| T_VARIABLE '[' encaps_var_offset ']' { $$ = Expr\ArrayDimFetch[Expr\Variable[parseVar($1)], $3]; }
| T_VARIABLE T_OBJECT_OPERATOR T_STRING { $$ = Expr\PropertyFetch[Expr\Variable[parseVar($1)], $3]; }
;
encaps_var:
encaps_base_var { $$ = $1; }
| encaps_base_var '[' encaps_var_offset ']' { $$ = Expr\ArrayDimFetch[$1, $3]; }
| encaps_base_var T_OBJECT_OPERATOR T_STRING { $$ = Expr\PropertyFetch[$1, $3]; }
| T_DOLLAR_OPEN_CURLY_BRACES expr '}' { $$ = Expr\Variable[$2]; }
| T_DOLLAR_OPEN_CURLY_BRACES T_STRING_VARNAME '}' { $$ = Expr\Variable[$2]; }
| T_DOLLAR_OPEN_CURLY_BRACES T_STRING_VARNAME '[' expr ']' '}'
@ -854,7 +875,8 @@ encaps_var:
encaps_var_offset:
T_STRING { $$ = Scalar\String_[$1]; }
| T_NUM_STRING { $$ = Scalar\String_[$1]; }
| T_NUM_STRING { $$ = $this->parseNumString($1, attributes()); }
| '-' T_NUM_STRING { $$ = $this->parseNumString('-' . $2, attributes()); }
| T_VARIABLE { $$ = Expr\Variable[parseVar($1)]; }
;

View File

@ -124,6 +124,12 @@ function resolveMacros($code) {
return '$this->startAttributeStack[#1] + $this->endAttributes';
}
if ('stackAttributes' == $name) {
assertArgs(1, $args, $name);
return '$this->startAttributeStack[' . $args[0] . ']'
. ' + $this->endAttributeStack[' . $args[0] . ']';
}
if ('init' == $name) {
return '$$ = array(' . implode(', ', $args) . ')';
}

View File

@ -3,8 +3,6 @@
namespace PhpParser\Builder;
use PhpParser;
use PhpParser\Node;
use PhpParser\Node\Stmt;
abstract class Declaration extends PhpParser\BuilderAbstract
{

View File

@ -4,12 +4,13 @@ namespace PhpParser\Builder;
use PhpParser;
use PhpParser\Node;
use PhpParser\Node\Stmt;
abstract class FunctionLike extends Declaration
{
protected $returnByRef = false;
protected $params = array();
/** @var string|Node\Name|Node\NullableType|null */
protected $returnType = null;
/**
@ -60,7 +61,7 @@ abstract class FunctionLike extends Declaration
/**
* Sets the return type for PHP 7.
*
* @param string|Node\Name $type One of array, callable, string, int, float, bool, iterable,
* @param string|Node\Name|Node\NullableType $type One of array, callable, string, int, float, bool, iterable,
* or a class/interface name.
*
* @return $this The builder instance (for fluid interface)

View File

@ -10,6 +10,8 @@ class Method extends FunctionLike
{
protected $name;
protected $flags = 0;
/** @var array|null */
protected $stmts = array();
/**

View File

@ -10,7 +10,10 @@ class Param extends PhpParser\BuilderAbstract
protected $name;
protected $default = null;
/** @var string|Node\Name|Node\NullableType|null */
protected $type = null;
protected $byRef = false;
/**
@ -38,7 +41,7 @@ class Param extends PhpParser\BuilderAbstract
/**
* Sets type hint for the parameter.
*
* @param string|Node\Name $type Type hint to use
* @param string|Node\Name|Node\NullableType $type Type hint to use
*
* @return $this The builder instance (for fluid interface)
*/

View File

@ -3,7 +3,6 @@
namespace PhpParser\Builder;
use PhpParser;
use PhpParser\Node\Name;
use PhpParser\Node\Stmt;
class Trait_ extends Declaration

View File

@ -2,12 +2,12 @@
namespace PhpParser;
use PhpParser\Node\Name;
use PhpParser\Node\Expr;
use PhpParser\Node\NullableType;
use PhpParser\Node\Stmt;
use PhpParser\Node\Scalar;
use PhpParser\Comment;
use PhpParser\Node\Expr;
use PhpParser\Node\Name;
use PhpParser\Node\NullableType;
use PhpParser\Node\Scalar;
use PhpParser\Node\Stmt;
abstract class BuilderAbstract implements Builder {
/**
@ -60,9 +60,9 @@ abstract class BuilderAbstract implements Builder {
* In particular, builtin types are left as strings, custom types become Names and nullables
* are wrapped in NullableType nodes.
*
* @param Name|string $type The type to normalize
* @param Name|string|NullableType $type The type to normalize
*
* @return Name|string The normalized type
* @return Name|string|NullableType The normalized type
*/
protected function normalizeType($type) {
if (!is_string($type)) {

View File

@ -61,6 +61,16 @@ class Error extends \RuntimeException
return $this->attributes;
}
/**
* Sets the attributes of the node/token the error occured at.
*
* @param array $attributes
*/
public function setAttributes(array $attributes) {
$this->attributes = $attributes;
$this->updateMessage();
}
/**
* Sets the line of the PHP file the error occurred in.
*
@ -120,6 +130,14 @@ class Error extends \RuntimeException
return $this->toColumn($code, $this->attributes['endFilePos']);
}
public function getMessageWithColumnInfo($code) {
return sprintf(
'%s from %d:%d to %d:%d', $this->getRawMessage(),
$this->getStartLine(), $this->getStartColumn($code),
$this->getEndLine(), $this->getEndColumn($code)
);
}
private function toColumn($code, $pos) {
if ($pos > strlen($code)) {
throw new \RuntimeException('Invalid position information');
@ -145,14 +163,4 @@ class Error extends \RuntimeException
$this->message .= ' on line ' . $this->getStartLine();
}
}
/** @deprecated Use getStartLine() instead */
public function getRawLine() {
return $this->getStartLine();
}
/** @deprecated Use setStartLine() instead */
public function setRawLine($line) {
$this->setStartLine($line);
}
}

View File

@ -0,0 +1,13 @@
<?php
namespace PhpParser;
interface ErrorHandler
{
/**
* Handle an error generated during lexing, parsing or some other operation.
*
* @param Error $error The error that needs to be handled
*/
public function handleError(Error $error);
}

View File

@ -0,0 +1,46 @@
<?php
namespace PhpParser\ErrorHandler;
use PhpParser\Error;
use PhpParser\ErrorHandler;
/**
* Error handler that collects all errors into an array.
*
* This allows graceful handling of errors.
*/
class Collecting implements ErrorHandler
{
/** @var Error[] Collected errors */
private $errors = [];
public function handleError(Error $error) {
$this->errors[] = $error;
}
/**
* Get collected errors.
*
* @return Error[]
*/
public function getErrors() {
return $this->errors;
}
/**
* Check whether there are any errors.
*
* @return bool
*/
public function hasErrors() {
return !empty($this->errors);
}
/**
* Reset/clear collected errors.
*/
public function clearErrors() {
$this->errors = [];
}
}

View File

@ -0,0 +1,18 @@
<?php
namespace PhpParser\ErrorHandler;
use PhpParser\Error;
use PhpParser\ErrorHandler;
/**
* Error handler that handles all errors by throwing them.
*
* This is the default strategy used by all components.
*/
class Throwing implements ErrorHandler
{
public function handleError(Error $error) {
throw $error;
}
}

View File

@ -2,7 +2,6 @@
namespace PhpParser;
use PhpParser\Node\Scalar\LNumber;
use PhpParser\Parser\Tokens;
class Lexer
@ -49,19 +48,16 @@ class Lexer
/**
* Initializes the lexer for lexing the provided source code.
*
* @param string $code The source code to lex
* This function does not throw if lexing errors occur. Instead, errors may be retrieved using
* the getErrors() method.
*
* @throws Error on lexing errors (unterminated comment or unexpected character)
* @param string $code The source code to lex
* @param ErrorHandler|null $errorHandler Error handler to use for lexing errors. Defaults to
* ErrorHandler\Throwing
*/
public function startLexing($code) {
$scream = ini_set('xdebug.scream', '0');
$this->resetErrors();
$this->tokens = @token_get_all($code);
$this->handleErrors();
if (false !== $scream) {
ini_set('xdebug.scream', $scream);
public function startLexing($code, ErrorHandler $errorHandler = null) {
if (null === $errorHandler) {
$errorHandler = new ErrorHandler\Throwing();
}
$this->code = $code; // keep the code around for __halt_compiler() handling
@ -72,6 +68,16 @@ class Lexer
// If inline HTML occurs without preceding code, treat it as if it had a leading newline.
// This ensures proper composability, because having a newline is the "safe" assumption.
$this->prevCloseTagHasNewline = true;
$scream = ini_set('xdebug.scream', '0');
$this->resetErrors();
$this->tokens = @token_get_all($code);
$this->handleErrors($errorHandler);
if (false !== $scream) {
ini_set('xdebug.scream', $scream);
}
}
protected function resetErrors() {
@ -85,32 +91,109 @@ class Lexer
}
}
protected function handleErrors() {
private function handleInvalidCharacterRange($start, $end, $line, ErrorHandler $errorHandler) {
for ($i = $start; $i < $end; $i++) {
$chr = $this->code[$i];
if ($chr === 'b' || $chr === 'B') {
// HHVM does not treat b" tokens correctly, so ignore these
continue;
}
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)
);
}
$errorHandler->handleError(new Error($errorMsg, [
'startLine' => $line,
'endLine' => $line,
'startFilePos' => $i,
'endFilePos' => $i,
]));
}
}
private function isUnterminatedComment($token) {
return ($token[0] === T_COMMENT || $token[0] === T_DOC_COMMENT)
&& substr($token[1], 0, 2) === '/*'
&& substr($token[1], -2) !== '*/';
}
private function errorMayHaveOccurred() {
if (defined('HHVM_VERSION')) {
// In HHVM token_get_all() does not throw warnings, so we need to conservatively
// assume that an error occurred
return true;
}
$error = error_get_last();
if (null === $error) {
return null !== $error
&& false === strpos($error['message'], 'Undefined variable');
}
protected function handleErrors(ErrorHandler $errorHandler) {
if (!$this->errorMayHaveOccurred()) {
return;
}
if (preg_match(
'~^Unterminated comment starting line ([0-9]+)$~',
$error['message'], $matches
)) {
throw new Error('Unterminated comment', (int) $matches[1]);
// 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.
$filePos = 0;
$line = 1;
foreach ($this->tokens as $i => $token) {
$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);
$this->handleInvalidCharacterRange(
$filePos, $nextFilePos, $line, $errorHandler);
$filePos = $nextFilePos;
}
$filePos += $tokenLen;
$line += substr_count($tokenValue, "\n");
}
if (preg_match(
'~^Unexpected character in input: \'(.)\' \(ASCII=([0-9]+)\)~s',
$error['message'], $matches
)) {
throw new Error(sprintf(
'Unexpected character "%s" (ASCII %d)',
$matches[1], $matches[2]
));
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
$this->handleInvalidCharacterRange(
$filePos, \strlen($this->code), $line, $errorHandler);
}
return;
}
// PHP cuts error message after null byte, so need special case
if (preg_match('~^Unexpected character in input: \'$~', $error['message'])) {
throw new Error('Unexpected null byte');
// 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,
]));
}
}

View File

@ -2,11 +2,9 @@
namespace PhpParser\Lexer;
use PhpParser\ErrorHandler;
use PhpParser\Parser\Tokens;
/**
* ATTENTION: This code is WRITE-ONLY. Do not try to read it.
*/
class Emulative extends \PhpParser\Lexer
{
protected $newKeywords;
@ -53,131 +51,106 @@ class Emulative extends \PhpParser\Lexer
$this->tokenMap[self::T_POW_EQUAL] = Tokens::T_POW_EQUAL;
}
public function startLexing($code) {
public function startLexing($code, ErrorHandler $errorHandler = null) {
$this->inObjectAccess = false;
$preprocessedCode = $this->preprocessCode($code);
parent::startLexing($preprocessedCode);
if ($preprocessedCode !== $code) {
$this->postprocessTokens();
parent::startLexing($code, $errorHandler);
if ($this->requiresEmulation($code)) {
$this->emulateTokens();
}
// Set code property back to the original code, so __halt_compiler()
// handling and (start|end)FilePos attributes use the correct offsets
$this->code = $code;
}
/*
* Replaces new features in the code by ~__EMU__{NAME}__{DATA}__~ sequences.
* ~LABEL~ is never valid PHP code, that's why we can (to some degree) safely
* use it here.
* Later when preprocessing the tokens these sequences will either be replaced
* by real tokens or replaced with their original content (e.g. if they occurred
* inside a string, i.e. a place where they don't have a special meaning).
* Checks if the code is potentially using features that require emulation.
*/
protected function preprocessCode($code) {
protected function requiresEmulation($code) {
if (version_compare(PHP_VERSION, self::PHP_7_0, '>=')) {
return $code;
return false;
}
$code = str_replace('??', '~__EMU__COALESCE__~', $code);
$code = str_replace('<=>', '~__EMU__SPACESHIP__~', $code);
$code = preg_replace_callback('(yield[ \n\r\t]+from)', function($matches) {
// Encoding $0 in order to preserve exact whitespace
return '~__EMU__YIELDFROM__' . bin2hex($matches[0]) . '__~';
}, $code);
if (preg_match('(\?\?|<=>|yield[ \n\r\t]+from)', $code)) {
return true;
}
if (version_compare(PHP_VERSION, self::PHP_5_6, '>=')) {
return $code;
return false;
}
$code = str_replace('...', '~__EMU__ELLIPSIS__~', $code);
$code = preg_replace('((?<!/)\*\*=)', '~__EMU__POWEQUAL__~', $code);
$code = preg_replace('((?<!/)\*\*(?!/))', '~__EMU__POW__~', $code);
return $code;
return preg_match('(\.\.\.|(?<!/)\*\*(?!/))', $code);
}
/*
* Replaces the ~__EMU__...~ sequences with real tokens or their original
* value.
* Emulates tokens for newer PHP versions.
*/
protected function postprocessTokens() {
// we need to manually iterate and manage a count because we'll change
protected function emulateTokens() {
// 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($this->tokens); $i < $c; ++$i) {
// first check that the following tokens are of form ~LABEL~,
// then match the __EMU__... sequence.
if ('~' === $this->tokens[$i]
&& isset($this->tokens[$i + 2])
&& '~' === $this->tokens[$i + 2]
&& T_STRING === $this->tokens[$i + 1][0]
&& preg_match('(^__EMU__([A-Z]++)__(?:([A-Za-z0-9]++)__)?$)', $this->tokens[$i + 1][1], $matches)
) {
if ('ELLIPSIS' === $matches[1]) {
$replace = array(
array(self::T_ELLIPSIS, '...', $this->tokens[$i + 1][2])
);
} else if ('POW' === $matches[1]) {
$replace = array(
array(self::T_POW, '**', $this->tokens[$i + 1][2])
);
} else if ('POWEQUAL' === $matches[1]) {
$replace = array(
array(self::T_POW_EQUAL, '**=', $this->tokens[$i + 1][2])
);
} else if ('COALESCE' === $matches[1]) {
$replace = array(
array(self::T_COALESCE, '??', $this->tokens[$i + 1][2])
);
} else if ('SPACESHIP' === $matches[1]) {
$replace = array(
array(self::T_SPACESHIP, '<=>', $this->tokens[$i + 1][2]),
);
} else if ('YIELDFROM' === $matches[1]) {
$content = hex2bin($matches[2]);
$replace = array(
array(self::T_YIELD_FROM, $content, $this->tokens[$i + 1][2] - substr_count($content, "\n"))
);
} else {
throw new \RuntimeException('Invalid __EMU__ sequence');
$replace = null;
if (isset($this->tokens[$i + 1])) {
if ($this->tokens[$i] === '?' && $this->tokens[$i + 1] === '?') {
array_splice($this->tokens, $i, 2, array(
array(self::T_COALESCE, '??', $line)
));
$c--;
continue;
}
if ($this->tokens[$i][0] === T_IS_SMALLER_OR_EQUAL
&& $this->tokens[$i + 1] === '>'
) {
array_splice($this->tokens, $i, 2, array(
array(self::T_SPACESHIP, '<=>', $line)
));
$c--;
continue;
}
if ($this->tokens[$i] === '*' && $this->tokens[$i + 1] === '*') {
array_splice($this->tokens, $i, 2, array(
array(self::T_POW, '**', $line)
));
$c--;
continue;
}
if ($this->tokens[$i] === '*' && $this->tokens[$i + 1][0] === T_MUL_EQUAL) {
array_splice($this->tokens, $i, 2, array(
array(self::T_POW_EQUAL, '**=', $line)
));
$c--;
continue;
}
array_splice($this->tokens, $i, 3, $replace);
$c -= 3 - count($replace);
// for multichar tokens (e.g. strings) replace any ~__EMU__...~ sequences
// in their content with the original character sequence
} elseif (is_array($this->tokens[$i])
&& 0 !== strpos($this->tokens[$i][1], '__EMU__')
) {
$this->tokens[$i][1] = preg_replace_callback(
'(~__EMU__([A-Z]++)__(?:([A-Za-z0-9]++)__)?~)',
array($this, 'restoreContentCallback'),
$this->tokens[$i][1]
);
}
}
}
/*
* This method is a callback for restoring EMU sequences in
* multichar tokens (like strings) to their original value.
*/
public function restoreContentCallback(array $matches) {
if ('ELLIPSIS' === $matches[1]) {
return '...';
} else if ('POW' === $matches[1]) {
return '**';
} else if ('POWEQUAL' === $matches[1]) {
return '**=';
} else if ('COALESCE' === $matches[1]) {
return '??';
} else if ('SPACESHIP' === $matches[1]) {
return '<=>';
} else if ('YIELDFROM' === $matches[1]) {
return hex2bin($matches[2]);
} else {
return $matches[0];
if (isset($this->tokens[$i + 2])) {
if ($this->tokens[$i][0] === T_YIELD && $this->tokens[$i + 1][0] === T_WHITESPACE
&& $this->tokens[$i + 2][0] === T_STRING
&& !strcasecmp($this->tokens[$i + 2][1], 'from')
) {
array_splice($this->tokens, $i, 3, array(
array(
self::T_YIELD_FROM,
$this->tokens[$i][1] . $this->tokens[$i + 1][1] . $this->tokens[$i + 2][1],
$line
)
));
$c -= 2;
$line += substr_count($this->tokens[$i][1], "\n");
continue;
}
if ($this->tokens[$i] === '.' && $this->tokens[$i + 1] === '.'
&& $this->tokens[$i + 2] === '.'
) {
array_splice($this->tokens, $i, 3, array(
array(self::T_ELLIPSIS, '...', $line)
));
$c -= 2;
continue;
}
}
if (\is_array($this->tokens[$i])) {
$line += substr_count($this->tokens[$i][1], "\n");
}
}
}

View File

@ -41,6 +41,15 @@ interface Node
*/
public function getDocComment();
/**
* Sets the doc comment of the node.
*
* This will either replace an existing doc comment or add it to the comments array.
*
* @param Comment\Doc $docComment Doc comment to set
*/
public function setDocComment(Comment\Doc $docComment);
/**
* Sets an attribute on a node.
*

View File

@ -24,7 +24,7 @@ class ArrayDimFetch extends Expr
$this->dim = $dim;
}
public function getSubnodeNames() {
public function getSubNodeNames() {
return array('var', 'dim');
}
}

View File

@ -2,22 +2,22 @@
namespace PhpParser\Node\Expr;
use PhpParser\Node\Name;
use PhpParser\Node\Expr;
use PhpParser\Node\Name;
class ClassConstFetch extends Expr
{
/** @var Name|Expr Class name */
public $class;
/** @var string Constant name */
/** @var string|Error Constant name */
public $name;
/**
* Constructs a class const fetch node.
*
* @param Name|Expr $class Class name
* @param string $name Constant name
* @param array $attributes Additional attributes
* @param Name|Expr $class Class name
* @param string|Error $name Constant name
* @param array $attributes Additional attributes
*/
public function __construct($class, $name, array $attributes = array()) {
parent::__construct($attributes);

View File

@ -16,7 +16,7 @@ class Closure extends Expr implements FunctionLike
public $params;
/** @var ClosureUse[] use()s */
public $uses;
/** @var null|string|Node\Name Return type */
/** @var null|string|Node\Name|Node\NullableType Return type */
public $returnType;
/** @var Node[] Statements */
public $stmts;

View File

@ -2,8 +2,8 @@
namespace PhpParser\Node\Expr;
use PhpParser\Node\Name;
use PhpParser\Node\Expr;
use PhpParser\Node\Name;
class ConstFetch extends Expr
{

View File

@ -2,8 +2,8 @@
namespace PhpParser\Node\Expr;
use PhpParser\Node\Name;
use PhpParser\Node\Expr;
use PhpParser\Node\Name;
class Instanceof_ extends Expr
{

View File

@ -2,8 +2,8 @@
namespace PhpParser\Node\Expr;
use PhpParser\Node\Name;
use PhpParser\Node\Expr;
use PhpParser\Node\Name;
class StaticPropertyFetch extends Expr
{

View File

@ -23,7 +23,7 @@ interface FunctionLike extends Node
/**
* Get the declared return type or null
*
* @return null|string|Node\Name
* @return null|string|Node\Name|Node\NullableType
*/
public function getReturnType();

View File

@ -6,22 +6,21 @@ use PhpParser\NodeAbstract;
class Name extends NodeAbstract
{
/** @var string[] Parts of the name */
/**
* @var string[] Parts of the name
* @deprecated Avoid directly accessing $parts, use methods instead.
*/
public $parts;
/**
* Constructs a name node.
*
* @param string|array $parts Parts of the name (or name as string)
* @param array $attributes Additional attributes
* @param string|array|self $name Name as string, part array or Name instance (copy ctor)
* @param array $attributes Additional attributes
*/
public function __construct($parts, array $attributes = array()) {
if (!is_array($parts)) {
$parts = explode('\\', $parts);
}
public function __construct($name, array $attributes = array()) {
parent::__construct($attributes);
$this->parts = $parts;
$this->parts = self::prepareName($name);
}
public function getSubNodeNames() {
@ -83,14 +82,13 @@ class Name extends NodeAbstract
}
/**
* Returns a string representation of the name by imploding the namespace parts with a separator.
*
* @param string $separator The separator to use (defaults to the namespace separator \)
* Returns a string representation of the name by imploding the namespace parts with the
* namespace separator.
*
* @return string String representation
*/
public function toString($separator = '\\') {
return implode($separator, $this->parts);
public function toString() {
return implode('\\', $this->parts);
}
/**
@ -103,43 +101,21 @@ class Name extends NodeAbstract
return implode('\\', $this->parts);
}
/**
* Prepends a name to this name.
*
* @deprecated Use Name::concat($name1, $name2) instead
*
* @param string|array|self $name Name to prepend
*/
public function prepend($name) {
$this->parts = array_merge(self::prepareName($name), $this->parts);
}
/**
* Appends a name to this name.
*
* @deprecated Use Name::concat($name1, $name2) instead
*
* @param string|array|self $name Name to append
*/
public function append($name) {
$this->parts = array_merge($this->parts, self::prepareName($name));
}
/**
* Gets a slice of a name (similar to array_slice).
*
* This method returns a new instance of the same type as the original and with the same
* attributes.
*
* If the slice is empty, a Name with an empty parts array is returned. While this is
* meaningless in itself, it works correctly in conjunction with concat().
* If the slice is empty, null is returned. The null value will be correctly handled in
* concatenations using concat().
*
* Offset and length have the same meaning as in array_slice().
*
* @param int $offset Offset to start the slice at (may be negative)
* @param int|null $length Length of the slice (may be negative)
*
* @return static Sliced name
* @return static|null Sliced name
*/
public function slice($offset, $length = null) {
$numParts = count($this->parts);
@ -158,6 +134,11 @@ class Name extends NodeAbstract
}
}
if ($realLength === 0) {
// Empty slice is represented as null
return null;
}
return new static(array_slice($this->parts, $realOffset, $realLength), $this->attributes);
}
@ -167,16 +148,29 @@ class Name extends NodeAbstract
* The type of the generated instance depends on which class this method is called on, for
* example Name\FullyQualified::concat() will yield a Name\FullyQualified instance.
*
* @param string|array|self $name1 The first name
* @param string|array|self $name2 The second name
* @param array $attributes Attributes to assign to concatenated name
* If one of the arguments is null, a new instance of the other name will be returned. If both
* arguments are null, null will be returned. As such, writing
* Name::concat($namespace, $shortName)
* where $namespace is a Name node or null will work as expected.
*
* @return static Concatenated name
* @param string|array|self|null $name1 The first name
* @param string|array|self|null $name2 The second name
* @param array $attributes Attributes to assign to concatenated name
*
* @return static|null Concatenated name
*/
public static function concat($name1, $name2, array $attributes = []) {
return new static(
array_merge(self::prepareName($name1), self::prepareName($name2)), $attributes
);
if (null === $name1 && null === $name2) {
return null;
} elseif (null === $name1) {
return new static(self::prepareName($name2), $attributes);
} else if (null === $name2) {
return new static(self::prepareName($name1), $attributes);
} else {
return new static(
array_merge(self::prepareName($name1), self::prepareName($name2)), $attributes
);
}
}
/**
@ -188,16 +182,16 @@ class Name extends NodeAbstract
* @return array Prepared name
*/
private static function prepareName($name) {
if (is_string($name)) {
if (\is_string($name)) {
return explode('\\', $name);
} elseif (is_array($name)) {
} elseif (\is_array($name)) {
return $name;
} elseif ($name instanceof self) {
return $name->parts;
}
throw new \InvalidArgumentException(
'When changing a name you need to pass either a string, an array or a Name node'
'Expected string, array of parts or Name instance'
);
}
}

View File

@ -2,12 +2,11 @@
namespace PhpParser\Node;
use PhpParser\Error;
use PhpParser\NodeAbstract;
class Param extends NodeAbstract
{
/** @var null|string|Name Typehint */
/** @var null|string|Name|NullableType Typehint */
public $type;
/** @var bool Whether parameter is passed by reference */
public $byRef;
@ -21,12 +20,12 @@ class Param extends NodeAbstract
/**
* Constructs a parameter node.
*
* @param string $name Name
* @param null|Expr $default Default value
* @param null|string|Name $type Typehint
* @param bool $byRef Whether is passed by reference
* @param bool $variadic Whether this is a variadic argument
* @param array $attributes Additional attributes
* @param string $name Name
* @param null|Expr $default Default value
* @param null|string|Name|NullableType $type Typehint
* @param bool $byRef Whether is passed by reference
* @param bool $variadic Whether this is a variadic argument
* @param array $attributes Additional attributes
*/
public function __construct($name, Expr $default = null, $type = null, $byRef = false, $variadic = false, array $attributes = array()) {
parent::__construct($attributes);
@ -35,10 +34,6 @@ class Param extends NodeAbstract
$this->variadic = $variadic;
$this->name = $name;
$this->default = $default;
if ($variadic && null !== $default) {
throw new Error('Variadic parameter cannot have a default value', $default->getAttributes());
}
}
public function getSubNodeNames() {

View File

@ -2,11 +2,12 @@
namespace PhpParser\Node\Scalar;
use PhpParser\Node\Expr;
use PhpParser\Node\Scalar;
class Encapsed extends Scalar
{
/** @var array Encaps list */
/** @var Expr[] list of string parts */
public $parts;
/**

View File

@ -3,7 +3,6 @@
namespace PhpParser\Node\Stmt;
use PhpParser\Node;
use PhpParser\Error;
class ClassConst extends Node\Stmt
{
@ -20,16 +19,6 @@ class ClassConst extends Node\Stmt
* @param array $attributes Additional attributes
*/
public function __construct(array $consts, $flags = 0, array $attributes = array()) {
if ($flags & Class_::MODIFIER_STATIC) {
throw new Error("Cannot use 'static' as constant modifier");
}
if ($flags & Class_::MODIFIER_ABSTRACT) {
throw new Error("Cannot use 'abstract' as constant modifier");
}
if ($flags & Class_::MODIFIER_FINAL) {
throw new Error("Cannot use 'final' as constant modifier");
}
parent::__construct($attributes);
$this->flags = $flags;
$this->consts = $consts;

View File

@ -5,7 +5,7 @@ namespace PhpParser\Node\Stmt;
use PhpParser\Node;
abstract class ClassLike extends Node\Stmt {
/** @var string Name */
/** @var string|null Name */
public $name;
/** @var Node[] Statements */
public $stmts;

View File

@ -4,7 +4,6 @@ namespace PhpParser\Node\Stmt;
use PhpParser\Node;
use PhpParser\Node\FunctionLike;
use PhpParser\Error;
class ClassMethod extends Node\Stmt implements FunctionLike
{
@ -16,7 +15,7 @@ class ClassMethod extends Node\Stmt implements FunctionLike
public $name;
/** @var Node\Param[] Parameters */
public $params;
/** @var null|string|Node\Name Return type */
/** @var null|string|Node\Name|Node\NullableType Return type */
public $returnType;
/** @var Node[] Statements */
public $stmts;
@ -46,17 +45,6 @@ class ClassMethod extends Node\Stmt implements FunctionLike
$this->params = isset($subNodes['params']) ? $subNodes['params'] : array();
$this->returnType = isset($subNodes['returnType']) ? $subNodes['returnType'] : null;
$this->stmts = array_key_exists('stmts', $subNodes) ? $subNodes['stmts'] : array();
if ($this->flags & Class_::MODIFIER_STATIC) {
switch (strtolower($this->name)) {
case '__construct':
throw new Error(sprintf('Constructor %s() cannot be static', $this->name));
case '__destruct':
throw new Error(sprintf('Destructor %s() cannot be static', $this->name));
case '__clone':
throw new Error(sprintf('Clone method %s() cannot be static', $this->name));
}
}
}
public function getSubNodeNames() {

View File

@ -2,8 +2,8 @@
namespace PhpParser\Node\Stmt;
use PhpParser\Node;
use PhpParser\Error;
use PhpParser\Node;
class Class_ extends ClassLike
{
@ -52,26 +52,6 @@ class Class_ extends ClassLike
$this->extends = isset($subNodes['extends']) ? $subNodes['extends'] : null;
$this->implements = isset($subNodes['implements']) ? $subNodes['implements'] : array();
$this->stmts = isset($subNodes['stmts']) ? $subNodes['stmts'] : array();
if (null !== $this->name && isset(self::$specialNames[strtolower($this->name)])) {
throw new Error(sprintf('Cannot use \'%s\' as class name as it is reserved', $this->name));
}
if (isset(self::$specialNames[strtolower($this->extends)])) {
throw new Error(
sprintf('Cannot use \'%s\' as class name as it is reserved', $this->extends),
$this->extends->getAttributes()
);
}
foreach ($this->implements as $interface) {
if (isset(self::$specialNames[strtolower($interface)])) {
throw new Error(
sprintf('Cannot use \'%s\' as interface name as it is reserved', $interface),
$interface->getAttributes()
);
}
}
}
public function getSubNodeNames() {

View File

@ -3,6 +3,7 @@
namespace PhpParser\Node\Stmt;
use PhpParser\Node;
class Declare_ extends Node\Stmt
{
/** @var DeclareDeclare[] List of declares */

View File

@ -13,7 +13,7 @@ class Function_ extends Node\Stmt implements FunctionLike
public $name;
/** @var Node\Param[] Parameters */
public $params;
/** @var null|string|Node\Name Return type */
/** @var null|string|Node\Name|Node\NullableType Return type */
public $returnType;
/** @var Node[] Statements */
public $stmts;

View File

@ -2,8 +2,8 @@
namespace PhpParser\Node\Stmt;
use PhpParser\Node\Stmt;
use PhpParser\Node\Name;
use PhpParser\Node\Stmt;
class GroupUse extends Stmt
{

View File

@ -3,19 +3,12 @@
namespace PhpParser\Node\Stmt;
use PhpParser\Node;
use PhpParser\Error;
class Interface_ extends ClassLike
{
/** @var Node\Name[] Extended interfaces */
public $extends;
protected static $specialNames = array(
'self' => true,
'parent' => true,
'static' => true,
);
/**
* Constructs a class node.
*
@ -30,19 +23,6 @@ class Interface_ extends ClassLike
$this->name = $name;
$this->extends = isset($subNodes['extends']) ? $subNodes['extends'] : array();
$this->stmts = isset($subNodes['stmts']) ? $subNodes['stmts'] : array();
if (isset(self::$specialNames[strtolower($this->name)])) {
throw new Error(sprintf('Cannot use \'%s\' as class name as it is reserved', $this->name));
}
foreach ($this->extends as $interface) {
if (isset(self::$specialNames[strtolower($interface)])) {
throw new Error(
sprintf('Cannot use \'%s\' as interface name as it is reserved', $interface),
$interface->getAttributes()
);
}
}
}
public function getSubNodeNames() {

View File

@ -3,7 +3,6 @@
namespace PhpParser\Node\Stmt;
use PhpParser\Node;
use PhpParser\Error;
class Namespace_ extends Node\Stmt
{
@ -12,12 +11,6 @@ class Namespace_ extends Node\Stmt
/** @var Node[] Statements */
public $stmts;
protected static $specialNames = array(
'self' => true,
'parent' => true,
'static' => true,
);
/**
* Constructs a namespace node.
*
@ -29,21 +22,6 @@ class Namespace_ extends Node\Stmt
parent::__construct($attributes);
$this->name = $name;
$this->stmts = $stmts;
if (isset(self::$specialNames[strtolower($this->name)])) {
throw new Error(
sprintf('Cannot use \'%s\' as namespace name', $this->name),
$this->name->getAttributes()
);
}
if (null !== $this->stmts) {
foreach ($this->stmts as $stmt) {
if ($stmt instanceof self) {
throw new Error('Namespace declarations cannot be nested', $stmt->getAttributes());
}
}
}
}
public function getSubNodeNames() {

View File

@ -3,7 +3,6 @@
namespace PhpParser\Node\Stmt;
use PhpParser\Node;
use PhpParser\Error;
class Property extends Node\Stmt
{
@ -23,14 +22,6 @@ class Property extends Node\Stmt
* @param array $attributes Additional attributes
*/
public function __construct($flags, array $props, array $attributes = array()) {
if ($flags & Class_::MODIFIER_ABSTRACT) {
throw new Error('Properties cannot be declared abstract');
}
if ($flags & Class_::MODIFIER_FINAL) {
throw new Error('Properties cannot be declared final');
}
parent::__construct($attributes);
$this->flags = $flags;
$this->type = $flags;

View File

@ -3,7 +3,6 @@
namespace PhpParser\Node\Stmt;
use PhpParser\Node;
use PhpParser\Error;
class TryCatch extends Node\Stmt
{
@ -23,10 +22,6 @@ class TryCatch extends Node\Stmt
* @param array|null $attributes Additional attributes
*/
public function __construct(array $stmts, array $catches, Finally_ $finally = null, array $attributes = array()) {
if (empty($catches) && null === $finally) {
throw new Error('Cannot use try without catch or finally');
}
parent::__construct($attributes);
$this->stmts = $stmts;
$this->catches = $catches;

View File

@ -3,7 +3,6 @@
namespace PhpParser\Node\Stmt;
use PhpParser\Node;
use PhpParser\Error;
class UseUse extends Node\Stmt
{
@ -27,13 +26,6 @@ class UseUse extends Node\Stmt
$alias = $name->getLast();
}
if ('self' == strtolower($alias) || 'parent' == strtolower($alias)) {
throw new Error(sprintf(
'Cannot use %s as %s because \'%2$s\' is a special class name',
$name, $alias
));
}
parent::__construct($attributes);
$this->type = $type;
$this->name = $name;

View File

@ -63,6 +63,28 @@ abstract class NodeAbstract implements Node, \JsonSerializable
return $lastComment;
}
/**
* Sets the doc comment of the node.
*
* This will either replace an existing doc comment or add it to the comments array.
*
* @param Comment\Doc $docComment Doc comment to set
*/
public function setDocComment(Comment\Doc $docComment) {
$comments = $this->getAttribute('comments', []);
$numComments = count($comments);
if ($numComments > 0 && $comments[$numComments - 1] instanceof Comment\Doc) {
// Replace existing doc comment
$comments[$numComments - 1] = $docComment;
} else {
// Append new comment
$comments[] = $docComment;
}
$this->setAttribute('comments', $comments);
}
public function setAttribute($key, $value) {
$this->attributes[$key] = $value;
}

View File

@ -11,27 +11,46 @@ use PhpParser\Node\Stmt\UseUse;
class NodeDumper
{
private $dumpComments;
private $dumpPositions;
private $code;
/**
* Constructs a NodeDumper.
*
* @param array $options Boolean option 'dumpComments' controls whether comments should be
* dumped
* Supported options:
* * bool dumpComments: Whether comments should be dumped.
* * bool dumpPositions: Whether line/offset information should be dumped. To dump offset
* information, the code needs to be passed to dump().
*
* @param array $options Options (see description)
*/
public function __construct(array $options = []) {
$this->dumpComments = !empty($options['dumpComments']);
$this->dumpPositions = !empty($options['dumpPositions']);
}
/**
* Dumps a node or array.
*
* @param array|Node $node Node or array to dump
* @param array|Node $node Node or array to dump
* @param string|null $code Code corresponding to dumped AST. This only needs to be passed if
* the dumpPositions option is enabled and the dumping of node offsets
* is desired.
*
* @return string Dumped value
*/
public function dump($node) {
public function dump($node, $code = null) {
$this->code = $code;
return $this->dumpRecursive($node);
}
protected function dumpRecursive($node) {
if ($node instanceof Node) {
$r = $node->getType() . '(';
$r = $node->getType();
if ($this->dumpPositions && null !== $p = $this->dumpPosition($node)) {
$r .= $p;
}
$r .= '(';
foreach ($node->getSubNodeNames() as $key) {
$r .= "\n " . $key . ': ';
@ -55,12 +74,12 @@ class NodeDumper
$r .= $value;
}
} else {
$r .= str_replace("\n", "\n ", $this->dump($value));
$r .= str_replace("\n", "\n ", $this->dumpRecursive($value));
}
}
if ($this->dumpComments && $comments = $node->getAttribute('comments')) {
$r .= "\n comments: " . str_replace("\n", "\n ", $this->dump($comments));
$r .= "\n comments: " . str_replace("\n", "\n ", $this->dumpRecursive($comments));
}
} elseif (is_array($node)) {
$r = 'array(';
@ -77,7 +96,7 @@ class NodeDumper
} elseif (is_scalar($value)) {
$r .= $value;
} else {
$r .= str_replace("\n", "\n ", $this->dump($value));
$r .= str_replace("\n", "\n ", $this->dumpRecursive($value));
}
}
} elseif ($node instanceof Comment) {
@ -144,4 +163,34 @@ class NodeDumper
}
return $map[$type] . ' (' . $type . ')';
}
protected function dumpPosition(Node $node) {
if (!$node->hasAttribute('startLine') || !$node->hasAttribute('endLine')) {
return null;
}
$start = $node->getAttribute('startLine');
$end = $node->getAttribute('endLine');
if ($node->hasAttribute('startFilePos') && $node->hasAttribute('endFilePos')
&& null !== $this->code
) {
$start .= ':' . $this->toColumn($this->code, $node->getAttribute('startFilePos'));
$end .= ':' . $this->toColumn($this->code, $node->getAttribute('endFilePos'));
}
return "[$start - $end]";
}
// Copied from Error class
private function toColumn($code, $pos) {
if ($pos > strlen($code)) {
throw new \RuntimeException('Invalid position information');
}
$lineStartPos = strrpos($code, "\n", $pos - strlen($code));
if (false === $lineStartPos) {
$lineStartPos = -1;
}
return $pos - $lineStartPos;
}
}

View File

@ -5,23 +5,42 @@ namespace PhpParser;
class NodeTraverser implements NodeTraverserInterface
{
/**
* @var NodeVisitor[] Visitors
* If NodeVisitor::enterNode() returns DONT_TRAVERSE_CHILDREN, child nodes
* of the current node will not be traversed for any visitors.
*
* For subsequent visitors enterNode() will still be called on the current
* node and leaveNode() will also be invoked for the current node.
*/
protected $visitors;
const DONT_TRAVERSE_CHILDREN = 1;
/**
* @var bool
* If NodeVisitor::enterNode() or NodeVisitor::leaveNode() returns
* STOP_TRAVERSAL, traversal is aborted.
*
* The afterTraverse() method will still be invoked.
*/
private $cloneNodes;
const STOP_TRAVERSAL = 2;
/**
* If NodeVisitor::leaveNode() returns REMOVE_NODE for a node that occurs
* in an array, it will be removed from the array.
*
* For subsequent visitors leaveNode() will still be invoked for the
* removed node.
*/
const REMOVE_NODE = false;
/** @var NodeVisitor[] Visitors */
protected $visitors;
/** @var bool Whether traversal should be stopped */
protected $stopTraversal;
/**
* Constructs a node traverser.
*
* @param bool $cloneNodes Should the traverser clone the nodes when traversing the AST
*/
public function __construct($cloneNodes = false) {
public function __construct() {
$this->visitors = array();
$this->cloneNodes = $cloneNodes;
}
/**
@ -55,6 +74,8 @@ class NodeTraverser implements NodeTraverserInterface
* @return Node[] Traversed array of nodes
*/
public function traverse(array $nodes) {
$this->stopTraversal = false;
foreach ($this->visitors as $visitor) {
if (null !== $return = $visitor->beforeTraverse($nodes)) {
$nodes = $return;
@ -73,21 +94,23 @@ class NodeTraverser implements NodeTraverserInterface
}
protected function traverseNode(Node $node) {
if ($this->cloneNodes) {
$node = clone $node;
}
foreach ($node->getSubNodeNames() as $name) {
$subNode =& $node->$name;
if (is_array($subNode)) {
$subNode = $this->traverseArray($subNode);
if ($this->stopTraversal) {
break;
}
} elseif ($subNode instanceof Node) {
$traverseChildren = true;
foreach ($this->visitors as $visitor) {
$return = $visitor->enterNode($subNode);
if (self::DONT_TRAVERSE_CHILDREN === $return) {
$traverseChildren = false;
} else if (self::STOP_TRAVERSAL === $return) {
$this->stopTraversal = true;
break 2;
} else if (null !== $return) {
$subNode = $return;
}
@ -95,10 +118,17 @@ class NodeTraverser implements NodeTraverserInterface
if ($traverseChildren) {
$subNode = $this->traverseNode($subNode);
if ($this->stopTraversal) {
break;
}
}
foreach ($this->visitors as $visitor) {
if (null !== $return = $visitor->leaveNode($subNode)) {
$return = $visitor->leaveNode($subNode);
if (self::STOP_TRAVERSAL === $return) {
$this->stopTraversal = true;
break 2;
} else if (null !== $return) {
if (is_array($return)) {
throw new \LogicException(
'leaveNode() may only return an array ' .
@ -120,12 +150,18 @@ class NodeTraverser implements NodeTraverserInterface
foreach ($nodes as $i => &$node) {
if (is_array($node)) {
$node = $this->traverseArray($node);
if ($this->stopTraversal) {
break;
}
} elseif ($node instanceof Node) {
$traverseChildren = true;
foreach ($this->visitors as $visitor) {
$return = $visitor->enterNode($node);
if (self::DONT_TRAVERSE_CHILDREN === $return) {
$traverseChildren = false;
} else if (self::STOP_TRAVERSAL === $return) {
$this->stopTraversal = true;
break 2;
} else if (null !== $return) {
$node = $return;
}
@ -133,6 +169,9 @@ class NodeTraverser implements NodeTraverserInterface
if ($traverseChildren) {
$node = $this->traverseNode($node);
if ($this->stopTraversal) {
break;
}
}
foreach ($this->visitors as $visitor) {
@ -141,6 +180,9 @@ class NodeTraverser implements NodeTraverserInterface
if (self::REMOVE_NODE === $return) {
$doNodes[] = array($i, array());
break;
} else if (self::STOP_TRAVERSAL === $return) {
$this->stopTraversal = true;
break 2;
} elseif (is_array($return)) {
$doNodes[] = array($i, $return);
break;

View File

@ -4,24 +4,6 @@ namespace PhpParser;
interface NodeTraverserInterface
{
/**
* If NodeVisitor::enterNode() returns DONT_TRAVERSE_CHILDREN, child nodes
* of the current node will not be traversed for any visitors.
*
* For subsequent visitors enterNode() will still be called on the current
* node and leaveNode() will also be invoked for the current node.
*/
const DONT_TRAVERSE_CHILDREN = 1;
/**
* If NodeVisitor::leaveNode() returns REMOVE_NODE for a node that occurs
* in an array, it will be removed from the array.
*
* For subsequent visitors leaveNode() will still be invoked for the
* removed node.
*/
const REMOVE_NODE = false;
/**
* Adds a visitor.
*

View File

@ -21,12 +21,18 @@ interface NodeVisitor
* Called when entering a node.
*
* Return value semantics:
* * null: $node stays as-is
* * otherwise: $node is set to the return value
* * null
* => $node stays as-is
* * NodeTraverser::DONT_TRAVERSE_CHILDREN
* => Children of $node are not traversed. $node stays as-is
* * NodeTraverser::STOP_TRAVERSAL
* => Traversal is aborted. $node stays as-is
* * otherwise
* => $node is set to the return value
*
* @param Node $node Node
*
* @return null|Node Node
* @return null|int|Node Node
*/
public function enterNode(Node $node);
@ -34,14 +40,20 @@ interface NodeVisitor
* Called when leaving a node.
*
* Return value semantics:
* * null: $node stays as-is
* * false: $node is removed from the parent array
* * array: The return value is merged into the parent array (at the position of the $node)
* * otherwise: $node is set to the return value
* * null
* => $node stays as-is
* * NodeTraverser::REMOVE_NODE
* => $node is removed from the parent array
* * NodeTraverser::STOP_TRAVERSAL
* => Traversal is aborted. $node stays as-is
* * array (of Nodes)
* => The return value is merged into the parent array (at the position of the $node)
* * otherwise
* => $node is set to the return value
*
* @param Node $node Node
*
* @return null|Node|false|Node[] Node
* @return null|false|int|Node|Node[] Node
*/
public function leaveNode(Node $node);

View File

@ -2,13 +2,14 @@
namespace PhpParser\NodeVisitor;
use PhpParser\NodeVisitorAbstract;
use PhpParser\Error;
use PhpParser\ErrorHandler;
use PhpParser\Node;
use PhpParser\Node\Expr;
use PhpParser\Node\Name;
use PhpParser\Node\Name\FullyQualified;
use PhpParser\Node\Expr;
use PhpParser\Node\Stmt;
use PhpParser\NodeVisitorAbstract;
class NameResolver extends NodeVisitorAbstract
{
@ -18,6 +19,26 @@ class NameResolver extends NodeVisitorAbstract
/** @var array Map of format [aliasType => [aliasName => originalName]] */
protected $aliases;
/** @var ErrorHandler Error handler */
protected $errorHandler;
/** @var bool Whether to preserve original names */
protected $preserveOriginalNames;
/**
* Constructs a name resolution visitor.
*
* Options: If "preserveOriginalNames" is enabled, an "originalName" attribute will be added to
* all name nodes that underwent resolution.
*
* @param ErrorHandler|null $errorHandler Error handler
* @param array $options Options
*/
public function __construct(ErrorHandler $errorHandler = null, array $options = []) {
$this->errorHandler = $errorHandler ?: new ErrorHandler\Throwing;
$this->preserveOriginalNames = !empty($options['preserveOriginalNames']);
}
public function beforeTraverse(array $nodes) {
$this->resetState();
}
@ -99,7 +120,10 @@ class NameResolver extends NodeVisitorAbstract
}
}
}
} elseif ($node instanceof Node\NullableType) {
if ($node->type instanceof Name) {
$node->type = $this->resolveClassName($node->type);
}
}
}
@ -132,13 +156,14 @@ class NameResolver extends NodeVisitorAbstract
Stmt\Use_::TYPE_CONSTANT => 'const ',
);
throw new Error(
$this->errorHandler->handleError(new Error(
sprintf(
'Cannot use %s%s as %s because the name is already in use',
$typeStringMap[$type], $name, $use->alias
),
$use->getLine()
);
$use->getAttributes()
));
return;
}
$this->aliases[$type][$aliasName] = $name;
@ -157,15 +182,21 @@ class NameResolver extends NodeVisitorAbstract
}
protected function resolveClassName(Name $name) {
if ($this->preserveOriginalNames) {
// Save the original name
$originalName = $name;
$name = clone $originalName;
$name->setAttribute('originalName', $originalName);
}
// don't resolve special class names
if (in_array(strtolower($name->toString()), array('self', 'parent', 'static'))) {
if (!$name->isUnqualified()) {
throw new Error(
$this->errorHandler->handleError(new Error(
sprintf("'\\%s' is an invalid class name", $name->toString()),
$name->getLine()
);
$name->getAttributes()
));
}
return $name;
}
@ -181,15 +212,18 @@ class NameResolver extends NodeVisitorAbstract
return FullyQualified::concat($alias, $name->slice(1), $name->getAttributes());
}
if (null !== $this->namespace) {
// if no alias exists prepend current namespace
return FullyQualified::concat($this->namespace, $name, $name->getAttributes());
}
return new FullyQualified($name->parts, $name->getAttributes());
// if no alias exists prepend current namespace
return FullyQualified::concat($this->namespace, $name, $name->getAttributes());
}
protected function resolveOtherName(Name $name, $type) {
if ($this->preserveOriginalNames) {
// Save the original name
$originalName = $name;
$name = clone $originalName;
$name->setAttribute('originalName', $originalName);
}
// fully qualified names are already resolved
if ($name->isFullyQualified()) {
return $name;
@ -208,28 +242,28 @@ class NameResolver extends NodeVisitorAbstract
$aliasName = $name->getFirst();
}
if (!isset($this->aliases[$type][$aliasName])) {
// unqualified, unaliased names cannot be resolved at compile-time
return $name;
if (isset($this->aliases[$type][$aliasName])) {
// resolve unqualified aliases
return new FullyQualified($this->aliases[$type][$aliasName], $name->getAttributes());
}
// resolve unqualified aliases
return new FullyQualified($this->aliases[$type][$aliasName], $name->getAttributes());
if (null === $this->namespace) {
// outside of a namespace unaliased unqualified is same as fully qualified
return new FullyQualified($name, $name->getAttributes());
}
// unqualified names inside a namespace cannot be resolved at compile-time
// add the namespaced version of the name as an attribute
$name->setAttribute('namespacedName',
FullyQualified::concat($this->namespace, $name, $name->getAttributes()));
return $name;
}
if (null !== $this->namespace) {
// if no alias exists prepend current namespace
return FullyQualified::concat($this->namespace, $name, $name->getAttributes());
}
return new FullyQualified($name->parts, $name->getAttributes());
// if no alias exists prepend current namespace
return FullyQualified::concat($this->namespace, $name, $name->getAttributes());
}
protected function addNamespacedName(Node $node) {
if (null !== $this->namespace) {
$node->namespacedName = Name::concat($this->namespace, $node->name);
} else {
$node->namespacedName = new Name($node->name);
}
$node->namespacedName = Name::concat($this->namespace, $node->name);
}
}

View File

@ -7,18 +7,11 @@ interface Parser {
* Parses PHP code into a node tree.
*
* @param string $code The source code to parse
* @param ErrorHandler|null $errorHandler Error handler to use for lexer/parser errors, defaults
* to ErrorHandler\Throwing.
*
* @return Node[]|null Array of statements (or null if the 'throwOnError' option is disabled and the parser was
* unable to recover from an error).
*/
public function parse($code);
/**
* Get array of errors that occurred during the last parse.
*
* This method may only return multiple errors if the 'throwOnError' option is disabled.
*
* @return Error[]
*/
public function getErrors();
public function parse($code, ErrorHandler $errorHandler = null);
}

View File

@ -3,61 +3,52 @@
namespace PhpParser\Parser;
use PhpParser\Error;
use PhpParser\ErrorHandler;
use PhpParser\Parser;
class Multiple implements Parser {
/** @var Parser[] List of parsers to try, in order of preference */
private $parsers;
/** @var Error[] Errors collected during last parse */
private $errors;
/**
* Create a parser which will try multiple parsers in an order of preference.
*
* Parsers will be invoked in the order they're provided to the constructor. If one of the
* parsers runs without errors, it's output is returned. Otherwise the errors (and
* PhpParser\Error exception) of the first parser are used.
* parsers runs without throwing, it's output is returned. Otherwise the exception that the
* first parser generated is thrown.
*
* @param Parser[] $parsers
*/
public function __construct(array $parsers) {
$this->parsers = $parsers;
$this->errors = [];
}
public function parse($code) {
list($firstStmts, $firstErrors, $firstError) = $this->tryParse($this->parsers[0], $code);
if ($firstErrors === []) {
$this->errors = [];
public function parse($code, ErrorHandler $errorHandler = null) {
if (null === $errorHandler) {
$errorHandler = new ErrorHandler\Throwing;
}
list($firstStmts, $firstError) = $this->tryParse($this->parsers[0], $errorHandler, $code);
if ($firstError === null) {
return $firstStmts;
}
for ($i = 1, $c = count($this->parsers); $i < $c; ++$i) {
list($stmts, $errors) = $this->tryParse($this->parsers[$i], $code);
if ($errors === []) {
$this->errors = [];
list($stmts, $error) = $this->tryParse($this->parsers[$i], $errorHandler, $code);
if ($error === null) {
return $stmts;
}
}
$this->errors = $firstErrors;
if ($firstError) {
throw $firstError;
}
return $firstStmts;
throw $firstError;
}
public function getErrors() {
return $this->errors;
}
private function tryParse(Parser $parser, $code) {
private function tryParse(Parser $parser, ErrorHandler $errorHandler, $code) {
$stmts = null;
$error = null;
try {
$stmts = $parser->parse($code);
$stmts = $parser->parse($code, $errorHandler);
} catch (Error $error) {}
$errors = $parser->getErrors();
return [$stmts, $errors, $error];
return [$stmts, $error];
}
}

View File

@ -19,7 +19,7 @@ class Php5 extends \PhpParser\ParserAbstract
{
protected $tokenToSymbolMapSize = 392;
protected $actionTableSize = 1012;
protected $gotoTableSize = 650;
protected $gotoTableSize = 652;
protected $invalidSymbol = 157;
protected $errorSymbol = 1;
@ -235,104 +235,104 @@ class Php5 extends \PhpParser\ParserAbstract
protected $action = array(
672, 673, 674, 675, 676,-32766, 677, 678, 679, 715,
716, 216, 217, 218, 219, 220, 221, 222, 223, 224,
282, 225, 226, 227, 228, 229, 230, 231, 232, 233,
23, 225, 226, 227, 228, 229, 230, 231, 232, 233,
234, 235, 236,-32766,-32766,-32766,-32766,-32766,-32766,-32766,
-32766,-32767,-32767,-32767,-32767, 440, 237, 238,-32766,-32766,
-32766,-32767,-32767,-32767,-32767, 460, 237, 238,-32766,-32766,
-32766,-32766, 680,-32766, 0,-32766,-32766,-32766,-32766,-32766,
-32766,-32767,-32767,-32767,-32767,-32767, 681, 682, 683, 684,
685, 686, 687, 1172, 204, 747,-32766,-32766,-32766,-32766,
685, 686, 687, 909, 204, 747,-32766,-32766,-32766,-32766,
-32766, 419, 688, 689, 690, 691, 692, 693, 694, 695,
696, 697, 698, 718, 719, 720, 721, 722, 710, 711,
712, 713, 714, 699, 700, 701, 702, 703, 704, 705,
741, 742, 743, 744, 745, 746, 706, 707, 708, 709,
739, 730, 728, 729, 725, 726, 306, 717, 723, 724,
731, 732, 734, 733, 735, 736, 52, 53, 420, 54,
55, 727, 738, 737, 447, 56, 57, 339, 58,-32766,
55, 727, 738, 737, 282, 56, 57, 284, 58,-32766,
-32766,-32766,-32766,-32766,-32766,-32766,-32766,-32766, 7,-32767,
-32767,-32767,-32767, 50, 329, 1186, 585, 945, 946, 947,
944, 943, 942, 937, 1212, 27, 1214, 1213, 763, 764,
821, 59, 60,-32766,-32766,-32766, 762, 61, 1172, 62,
-32767,-32767,-32767, 50, 329, 900, 585, 945, 946, 947,
944, 943, 942, 937, 1213, 27, 1215, 1214, 763, 764,
821, 59, 60,-32766,-32766,-32766, 918, 61, 1172, 62,
291, 292, 63, 64, 65, 66, 67, 68, 69, 70,
441, 24, 299, 71, 413,-32766,-32766,-32766, 1185, 1087,
356, 24, 299, 71, 413,-32766,-32766,-32766, 1185, 1087,
1088, 749, 633, 1178, 213, 214, 215, 464,-32766,-32766,
-32766, 822, 407, 1099, 309,-32766, 1054,-32766,-32766,-32766,
-32766,-32766,-32766, 1036, 200, -269, 428, 1036,-32766, 23,
-32766, 822, 407, 1099, 311,-32766, 1054,-32766,-32766,-32766,
-32766,-32766,-32766, 1036, 200, -269, 428, 1036,-32766, 447,
-32766,-32766,-32766,-32766,-32766, 120, 491, 945, 946, 947,
944, 943, 942, 297, 473, 474, 283, 623, 125,-32766,
893, 894, 339, 477, 478,-32766, 1093, 1094, 1095, 1096,
1090, 1091, 307, 492,-32766, 8, 425, 492, 1097, 1092,
425, 121, -220, 869, 1182, 39, 280, 332, 321, 18,
425, 121, -220, 869, 1182, 39, 280, 332, 321, 1186,
322, 421, -122, -122, -122, -4, 822, 463, 99, 100,
101, 811, 301, 377, 38, 19, 422, -122, 465, -122,
466, -122, 467, -122, 102, 423, -122, -122, -122, 28,
29, 468, 424, 624, 30, 469, 425, 812, 72, 412,
923, 349, 350, 470, 471,-32766,-32766,-32766, 298, 472,
1036, 809, 793, 840, 475, 476,-32767,-32767,-32767,-32767,
1036, 808, 793, 840, 475, 476,-32767,-32767,-32767,-32767,
94, 95, 96, 97, 98,-32766, 126,-32766,-32766,-32766,
-32766, 1137, 213, 214, 215, 295, 421, 239, 824, 638,
-122, 1036, 463, 893, 894, 367, 811, 1036, 1204, 38,
19, 422, 200, 465, 900, 466, 492, 467, 127, 425,
-122, 1036, 463, 893, 894, 1205, 811, 1036, 1204, 38,
19, 422, 200, 465, 18, 466, 492, 467, 127, 425,
423, 213, 214, 215, 28, 29, 468, 424, 414, 30,
469, 1036, 870, 72, 320, 822, 349, 350, 470, 471,
1036, 200, 214, 215, 472, 356, 919, 755, 840, 475,
1036, 200, 214, 215, 472, 441, 919, 755, 840, 475,
476, 213, 214, 215, 295, -216, 76, 77, 78, 47,
338, 200, 477, 644, 348, 438, 31, 294, 331, 460,
326, 200, 241, 824, 638, -4, 32, 334, 79, 80,
338, 200, 477, 644, 348, 438, 31, 294, 331, 440,
326, 200, 241, 824, 638, -4, 32, 335, 79, 80,
81, 82, 83, 84, 85, 86, 87, 88, 89, 90,
91, 92, 93, 94, 95, 96, 97, 98, 99, 100,
101, 1209, 301, 119, 822, 421, 801, 124,-32766,-32766,
101, 1210, 301, 34, 822, 421, 801, 124,-32766,-32766,
-32766, 463, 899, 129, 102, 811, 1178, 576, 38, 19,
422, 545, 465, 909, 466, 34, 467, 46,-32766, 423,
422, 546, 465, 1172, 466, 119, 467, 49,-32766, 423,
-32766,-32766, 647, 28, 29, 468, 822, 803, 30, 469,
415, 117, 72, 805, 49, 349, 350,-32766,-32766,-32766,
415, 117, 72, 805, 46, 349, 350,-32766,-32766,-32766,
-32766,-32766,-32766, 472, 200, 1036, 234, 235, 236,-32766,
-32766,-32766,-32766,-32766, 641, 1138, 124,-32766,-32766,-32766,
-32766,-32766, 237, 238, 421, 96, 97, 98, 293,-32766,
463, 518, 856, 638, 811, 439, 808, 38, 19, 422,
332, 465, 650, 466, 749, 467, 1178, 284, 423, 231,
232, 233, 28, 29, 468, 822, 421, 30, 469, 116,
918, 72, 463, 215, 349, 350, 811, 242, 1036, 38,
19, 422, 472, 465, 207, 466, 118, 467, 1036, 1064,
-32766,-32766, 237, 238, 421, 231, 232, 233, 293,-32766,
463, 518, 856, 638, 811, 439, 809, 38, 19, 422,
332, 465, 128, 466, 749, 467, 1178, 339, 423, 96,
97, 98, 28, 29, 468, 822, 421, 30, 469, 118,
762, 72, 463, 215, 349, 350, 811, 242, 1036, 38,
19, 422, 472, 465, 206, 466, 116, 467, 1036, 1064,
423, 200, 833, 642, 28, 29, 468, 822, 1099, 30,
469, 296, 115, 72, 205, 123, 349, 350,-32766,-32766,
492, 824, 638, 425, 472, 206, 213, 214, 215, 434,
469, 296, 207, 72, 205, 123, 349, 350,-32766,-32766,
492, 824, 638, 425, 472, 115, 213, 214, 215, 434,
492, 244, 640, 425, 243, 653, 237, 238, 429,-32766,
332, 454, 591, 449, 20, 421, 200, 130, 357, 763,
764, 463, 646, 824, 638, 811, 922, 666, 38, 19,
422, 820, 465, 311, 466, 128, 467, 599, 600, 423,
764, 463, 820, 824, 638, 811, 922, 666, 38, 19,
422, 646, 465, 650, 466, 313, 467, 599, 600, 423,
102, 756, 643, 28, 29, 468, 822, 421, 30, 469,
934, 656, 72, 463, 301, 349, 350, 811, 44, 51,
934, 656, 72, 463, 301, 349, 350, 811, 41, 51,
38, 19, 422, 472, 465, 48, 466, 299, 467, 605,
43, 423, 41, 513, 45, 28, 29, 468, 42, 524,
30, 469, 596,-32766, 72, 749, 534, 349, 350, 512,
435, 433, 824, 638, 1207, 472, 750, 33, 103, 104,
42, 423, 43, 517, 44, 28, 29, 468, 45,-32766,
30, 469, 596, 524, 72, 435, 433, 349, 350, 533,
534, 749, 824, 638, 1208, 472, 776, 33, 103, 104,
105, 106, 107, 108, 109, 110, 111, 112, 113, 114,
776, 533, 517, 437, 632, 622, 421, 612, 1057, 602,
516, 619, 463, 279, 824, 638, 811, 595, 582, 38,
750, 513, 512, 437, 632, 602, 421, 1057, 622, 612,
516, 619, 463, 279, 824, 638, 811, 582, 595, 38,
19, 422, 330, 465, 579, 466, 240, 467, 975, 977,
423, 609, 327, 12, 28, 29, 468, 537, -80, 30,
423, 609, 334, 537, 28, 29, 468, 12, -80, 30,
469, 842, 432, 72, 208, 209, 349, 350, 458, 1144,
210, 384, 211, 333, 472, 841, 9, 393, -398, 409,
210, 384, 211, 327, 472, 841, 9, 393, -398, 409,
328, 0, 310, 1045, 202, 477, 0, 0, 0, 208,
209, 0, 1087, 1088, 0, 210,-32766, 211, -497, -498,
1089, 308, 0, 824, 638, 0, 1098, 4, 372, 202,
209, 0, 1087, 1088, 0, 210,-32766, 211, -497, 312,
1089, -498, 0, 824, 638, 0, 1098, 4, 372, 202,
3, 11, 303, 0, -407, 0, 370, 1087, 1088, -406,
-497,-32766, 434, 864, 526, 1089, 408, 385, 75, 835,
0, 857, 863, 854, 813, 798, 819, 807, 761, 661,
0, 660, 37, 36, 759, 926, 810, 565, 852, 1093,
1094, 1095, 1096, 1090, 1091, 383, 929, 930, 806, 760,
818, 1097, 1092, 928, 804, 302, 802, 927, 212, 796,
1094, 1095, 1096, 1090, 1091, 383, 929, 806, 930, 818,
760, 1097, 1092, 928, 804, 302, 802, 927, 212, 796,
-32766, 872, 565, 639, 1093, 1094, 1095, 1096, 1090, 1091,
383, 649, 651, 652, 654, 655, 1097, 1092, 658, 663,
664, 1034, 665, 212, 122,-32766, 324, 325, 405, 406,
-416, 1210, 757, 758, 839, 838, 766, 453, 1208, 1179,
-416, 1211, 757, 758, 839, 838, 766, 453, 1209, 1179,
1177, 1163, 1175, 1078, 911, 1183, 1173, 829, 836, 1038,
1039, 827, 935, 1211, 765, 837, 794, 662, 1050, 861,
1039, 827, 935, 1212, 765, 837, 794, 662, 1050, 861,
768, 767, 862, 0, 304, 290, 289, 25, 26, 281,
305, 335, 203, 74, 411, 417, 35, 73,-32766, 40,
305, 336, 203, 74, 411, 417, 35, 73,-32766, 40,
22, 0, 1015, 569, -217, 1016, 1103, 1080, 901, 1040,
1044, 1041, 629, 559, 461, 457, 455, 450, 378, 16,
15, 14, -216, 0, 0, 0, 603, 1157, 1104, 1206,
15, 14, -216, 0, 0, 0, 603, 1157, 1104, 1207,
1077, 1174, 1158, 1162, 1176, 1063, 1048, 1049, 1046, 1047,
0, 1143
);
@ -456,8 +456,8 @@ class Php5 extends \PhpParser\ParserAbstract
398, 398, 398, 398, 398, 398, 398, 398, 398, 398,
398, 398, 398, 398, 398, 398, 398, 398, 398, 398,
398, 398, 398, 398, 398, 398, 398, 398, 398, 398,
398, 254, 179, 460, 482, 738, 736, 730, 732, 827,
659, 823, 778, 779, 636, 780, 781, 782, 783, 784,
398, 254, 179, 482, 460, 738, 736, 729, 731, 827,
659, 823, 778, 779, 533, 780, 781, 782, 783, 784,
777, 785, 841, 786, 418, 418, 418, 418, 418, 418,
418, 418, 418, 418, 418, -3, 354, 383, 413, 206,
524, 618, 618, 618, 618, 618, 618, 618, 175, 175,
@ -471,42 +471,42 @@ class Php5 extends \PhpParser\ParserAbstract
327, 587, 210, 489, 197, 25, 25, 25, 25, 25,
17, 45, 5, 5, 5, 5, 712, 305, 305, 305,
305, 118, 118, 118, 118, 774, 775, 795, 798, 303,
303, 676, 676, 629, 765, 498, 498, 522, 522, 487,
303, 676, 676, 629, 765, 522, 522, 498, 498, 487,
487, 487, 487, 487, 487, 487, 487, 487, 487, 387,
156, 817, 130, 130, 130, 130, 243, 469, 207, 207,
207, 243, 650, 855, 248, 248, 248, 476, 476, 476,
76, 627, 296, 296, 547, 547, 547, 477, 477, 477,
477, 483, 699, 477, 477, 477, 362, 631, 97, 334,
662, 799, 661, 802, 508, 692, 96, 700, 653, 414,
680, 571, 569, 561, 656, 406, 414, 254, 532, 447,
630, 718, 639, 38, 716, 408, 193, 363, 523, 430,
-6, 137, 767, 731, 821, 820, 13, 321, 691, 630,
630, 630, 232, 84, 768, 769, 430, 273, 570, 570,
156, 817, 130, 130, 130, 130, 243, 469, 650, 855,
207, 207, 207, 243, 248, 248, 248, 476, 476, 476,
76, 631, 296, 296, 547, 547, 547, 477, 477, 477,
477, 483, 699, 627, 477, 477, 477, 362, 97, 334,
661, 799, 662, 802, 508, 692, 96, 700, 653, -6,
680, 571, 569, 561, 656, 406, -6, 254, 532, 430,
630, 718, 643, 432, 716, 193, 408, 363, 523, 447,
414, 232, 767, 732, 821, 820, 137, 321, 693, 630,
630, 630, 13, 84, 768, 769, 447, 273, 570, 570,
570, 570, 801, 770, 570, 570, 570, 570, 800, 794,
268, 432, 776, 74, 717, 638, 638, 638, 638, 638,
638, 567, 638, 807, 626, 819, 819, 664, 670, 567,
818, 818, 818, 818, 567, 638, 819, 819, 567, 629,
819, 168, 567, 646, 638, 667, 667, 818, 713, 711,
626, 671, 673, 819, 819, 819, 673, 664, 567, 818,
603, 702, 67, 819, 818, 634, 634, 603, 567, 634,
670, 634, 54, 632, 635, 815, 816, 814, 642, 744,
672, 658, 805, 804, 812, 665, 644, 808, 806, 698,
710, 709, 643, 597, 641, 633, 621, 637, 688, 628,
693, 680, 696, 604, 604, 604, 690, 685, 690, 604,
268, 38, 776, 74, 717, 644, 644, 644, 644, 644,
644, 567, 644, 807, 664, 819, 819, 642, 666, 567,
818, 818, 818, 818, 567, 644, 819, 819, 567, 629,
819, 168, 567, 667, 644, 646, 646, 818, 713, 711,
664, 671, 673, 819, 819, 819, 673, 642, 567, 818,
603, 702, 67, 819, 818, 636, 636, 603, 567, 636,
666, 636, 54, 623, 621, 815, 816, 814, 665, 744,
672, 658, 805, 804, 812, 626, 639, 808, 806, 698,
710, 709, 638, 610, 633, 635, 637, 641, 688, 628,
674, 680, 696, 604, 604, 604, 690, 685, 690, 604,
604, 604, 604, 604, 604, 604, 604, 648, 694, 697,
674, 657, 708, 599, 682, 683, 610, 759, 687, 698,
698, 796, 829, 836, 766, 757, 649, 734, 831, 690,
854, 715, 274, 579, 681, 797, 645, 663, 701, 690,
803, 690, 760, 690, 828, 647, 793, 698, 773, 604,
691, 657, 708, 599, 682, 679, 579, 759, 687, 698,
698, 796, 829, 836, 766, 757, 663, 734, 831, 690,
854, 715, 274, 597, 681, 797, 645, 649, 701, 690,
803, 690, 760, 690, 828, 793, 647, 698, 773, 604,
826, 853, 852, 851, 850, 849, 848, 847, 846, 622,
845, 707, 677, 835, 119, 811, 656, 654, 651, 706,
440, 844, 772, 690, 690, 761, 699, 690, 763, 743,
714, 839, 704, 834, 843, 687, 833, 690, 686, 842,
440, 623, 625, 822, 678, 695, 813, 668, 824, 810,
752, 572, 619, 771, 533, 741, 838, 837, 840, 703,
753, 754, 616, 660, 669, 666, 787, 755, 809, 705,
788, 789, 830, 684, 696, 689, 652, 679, 655, 756,
790, 832, 720, 728, 729, 791, 739, 792, 0, 0,
440, 632, 625, 822, 678, 695, 813, 670, 824, 810,
752, 572, 619, 771, 634, 741, 838, 837, 840, 703,
753, 754, 616, 660, 668, 669, 787, 755, 809, 705,
788, 789, 830, 684, 696, 689, 652, 683, 655, 756,
790, 832, 720, 728, 730, 791, 739, 792, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 134, 134,
@ -539,17 +539,17 @@ class Php5 extends \PhpParser\ParserAbstract
-20, -20, -20, 0, 248, 248, 248, 248, -20, -20,
-20, -20, 55, 55, 55, 55, 487, 487, 487, 487,
487, 487, 248, 248, 476, 476, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 487, 55, 487, 638,
638, 638, 638, 638, 296, 407, 407, 407, 638, 296,
296, 0, 0, 0, 0, 0, 0, 638, 296, 0,
0, 638, 638, 638, 638, 638, 638, 638, 638, 407,
638, 638, 638, 819, 296, 0, 407, 550, 550, 550,
550, 440, 430, 0, 638, 638, 0, 671, 0, 0,
0, 0, 0, 0, 0, 0, 487, 55, 487, 644,
644, 644, 644, 644, 296, 296, 296, 407, 407, 407,
644, 0, 0, 0, 0, 0, 0, 644, 296, 0,
0, 644, 644, 644, 644, 644, 644, 644, 644, 407,
296, 644, 644, 644, 819, 0, 407, 550, 550, 550,
550, 440, 447, 0, 644, 644, 0, 671, 0, 0,
0, 819, 0, 0, 0, 0, 0, 604, 274, 734,
0, 427, 0, 0, 0, 0, 0, 0, 0, 649,
0, 427, 0, 0, 0, 0, 0, 0, 0, 663,
427, 246, 246, 0, 0, 622, 604, 604, 604, 0,
0, 649, 649, 0, 0, 0, 0, 0, 0, 433,
649, 0, 0, 0, 0, 433, 322, 0, 0, 322,
0, 663, 663, 0, 0, 0, 0, 0, 0, 433,
663, 0, 0, 0, 0, 433, 322, 0, 0, 322,
0, 440
);
@ -587,10 +587,10 @@ class Php5 extends \PhpParser\ParserAbstract
272,32767, 228, 228, 228, 228, 228,32767,32767,32767,
32767,32767,32767,32767, 321, 322, 320, 448, 449, 447,
32767, 415,32767, 417,32767,32767,32767,32767,32767,32767,
32767,32767,32767,32767,32767,32767, 486,32767,32767,32767,
32767,32767,32767,32767,32767,32767,32767, 486,32767,32767,
32767,32767,32767,32767,32767, 499, 404,32767,32767,32767,
397, 212, 214, 161, 472,32767,32767,32767,32767, 504,
331,32767,32767,32767,32767,32767,32767, 538,32767, 499,
331,32767,32767,32767,32767,32767,32767, 539,32767, 499,
32767,32767,32767,32767,32767,32767,32767,32767, 344, 323,
324, 325,32767,32767,32767,32767, 503, 497, 456, 457,
458, 459,32767,32767, 450, 451, 452, 455,32767,32767,
@ -608,7 +608,7 @@ class Php5 extends \PhpParser\ParserAbstract
405,32767,32767,32767, 84, 108, 247,32767, 537, 84,
400,32767,32767, 294, 537,32767,32767,32767,32767, 532,
32767,32767, 288,32767,32767,32767, 84,32767, 84, 243,
32767, 163,32767, 522,32767,32767, 497, 401,32767, 328,
32767, 163,32767, 522,32767, 497,32767, 401,32767, 328,
32767,32767,32767,32767,32767,32767,32767,32767,32767, 498,
32767,32767,32767,32767, 219,32767, 435,32767, 84,32767,
179,32767,32767, 292, 238,32767,32767, 531,32767,32767,
@ -635,50 +635,49 @@ class Php5 extends \PhpParser\ParserAbstract
549, 182, 183, 184, 185, 186, 187, 188, 189, 190,
191, 192, 193, 194, 145, 146, 147, 161, 148, 163,
149, 197, 164, 150, 151, 152, 198, 153, 131, 625,
567, 784, 567, 567, 567, 567, 567, 567, 567, 567,
567, 753, 567, 567, 567, 567, 567, 567, 567, 567,
567, 567, 567, 567, 567, 567, 567, 567, 567, 567,
567, 567, 567, 567, 567, 567, 567, 567, 567, 567,
567, 567, 567, 567, 567, 567, 567, 567, 567, 567,
567, 567, 567, 567, 567, 1100, 753, 1100, 1100, 1100,
567, 567, 567, 567, 567, 1100, 754, 1100, 1100, 1100,
1100, 1100, 1100, 1100, 1100, 1100, 1100, 1100, 1100, 1100,
1100, 1100, 1100, 1100, 1100, 1100, 1100, 1100, 1100, 1100,
1100, 1100, 1100, 1100, 1100, 1100, 1100, 1100, 1100, 1100,
1100, 1100, 1100, 1100, 1100, 1100, 1100, 1100, 1100, 1100,
885, 885, 1190, 1190, 1169, 611, 1169, 168, 617, 509,
885, 885, 1190, 1190, 6, 611, 784, 168, 617, 509,
358, 509, 171, 172, 173, 388, 389, 390, 391, 167,
195, 199, 201, 249, 251, 253, 260, 261, 262, 263,
264, 265, 271, 272, 273, 274, 287, 288, 317, 318,
319, 394, 395, 396, 397, 169, 174, 246, 247, 175,
176, 177, 497, 497, 497, 497, 497, 497, 6, 523,
580, 606, 497, 497, 497, 497, 497, 497, 497, 497,
497, 497, 508, 754, 508, 387, 608, 543, 543, 573,
176, 177, 497, 497, 497, 497, 497, 497, 341, 580,
606, 523, 497, 497, 497, 497, 497, 497, 497, 497,
497, 497, 508, 634, 508, 387, 608, 543, 543, 573,
539, 1076, 1075, 790, 752, 541, 541, 496, 498, 529,
546, 574, 577, 587, 593, 871, 510, 851, 510, 657,
634, 511, 880, 875, 566, 341, 566, 566, 566, 566,
545, 574, 577, 587, 593, 871, 1169, 851, 1169, 657,
815, 511, 880, 875, 566, 855, 566, 566, 566, 566,
566, 566, 566, 566, 566, 566, 566, 566, 566, 566,
566, 566, 566, 566, 566, 566, 566, 566, 566, 566,
566, 566, 566, 566, 566, 566, 566, 566, 566, 566,
566, 566, 566, 566, 566, 566, 566, 566, 566, 551,
552, 553, 554, 555, 556, 557, 558, 560, 589, 815,
548, 550, 514, 572, 855, 522, 519, 519, 519, 443,
445, 933, 636, 519, 1161, 1101, 618, 931, 522, 522,
1201, 1201, 903, 430, 430, 430, 430, 430, 430, 538,
519, 1201, 1058, 430, 430, 430, 430, 430, 430, 430,
430, 430, 430, 1065, 544, 1065, 892, 892, 892, 892,
892, 535, 1194, 659, 562, 892, 594, 868, 882, 613,
867, 616, 878, 620, 621, 628, 630, 635, 637, 777,
361, 849, 849, 849, 849, 598, 1081, 844, 850, 615,
583, 586, 631, 1019, 17, 13, 355, 519, 519, 536,
568, 519, 519, 1149, 519, 607, 777, 777, 1187, 375,
561, 369, 369, 369, 1061, 1062, 1009, 520, 1058, 1168,
889, 1168, 21, 1200, 1200, 347, 547, 604, 1184, 1184,
1184, 1059, 1160, 1059, 1200, 369, 362, 590, 344, 404,
527, 1060, 386, 898, 540, 276, 277, 278, 1203, 323,
313, 907, 1167, 960, 398, 368, 773, 373, 645, 1051,
451, 771, 402, 1056, 770, 770, 778, 778, 778, 780,
10, 769, 446, 781, 578, 912, 342, 343, 859, 1146,
459, 949, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 941, 0, 0, 0,
552, 553, 554, 555, 556, 557, 558, 560, 589, 550,
323, 309, 514, 522, 1201, 1201, 519, 519, 519, 583,
586, 631, 510, 519, 510, 1161, 522, 522, 903, 1201,
590, 344, 404, 430, 430, 430, 430, 430, 430, 538,
519, 544, 1058, 430, 430, 430, 430, 430, 430, 430,
430, 430, 430, 1065, 1194, 1065, 892, 892, 892, 892,
892, 361, 598, 535, 777, 659, 562, 892, 594, 868,
882, 613, 867, 616, 878, 620, 621, 628, 630, 635,
637, 849, 849, 849, 849, 1149, 1081, 607, 844, 850,
615, 777, 777, 1019, 17, 13, 355, 519, 519, 536,
568, 519, 519, 1187, 519, 443, 445, 933, 636, 375,
561, 1101, 618, 931, 1061, 1062, 889, 520, 1058, 276,
277, 278, 21, 1200, 1200, 548, 547, 604, 572, 342,
343, 1059, 1160, 1059, 1168, 362, 1168, 1009, 1200, 527,
898, 1060, 941, 540, 347, 1184, 1184, 1184, 1203, 770,
770, 778, 778, 778, 780, 960, 769, 398, 373, 451,
369, 369, 369, 773, 368, 771, 645, 1167, 907, 10,
402, 1051, 1056, 446, 781, 578, 912, 859, 1146, 459,
949, 0, 369, 0, 0, 0, 0, 0, 0, 386,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
@ -688,7 +687,9 @@ class Php5 extends \PhpParser\ParserAbstract
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 528
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 528
);
protected $gotoCheck = array(
@ -703,50 +704,49 @@ class Php5 extends \PhpParser\ParserAbstract
39, 39, 39, 39, 39, 39, 39, 39, 39, 39,
39, 39, 39, 39, 39, 39, 39, 39, 39, 39,
39, 39, 39, 39, 39, 39, 39, 39, 39, 53,
112, 25, 112, 112, 112, 112, 112, 112, 112, 112,
112, 11, 112, 112, 112, 112, 112, 112, 112, 112,
112, 112, 112, 112, 112, 112, 112, 112, 112, 112,
112, 112, 112, 112, 112, 112, 112, 112, 112, 112,
112, 112, 112, 112, 112, 112, 112, 112, 112, 112,
112, 112, 112, 112, 112, 119, 11, 119, 119, 119,
112, 112, 112, 112, 112, 119, 12, 119, 119, 119,
119, 119, 119, 119, 119, 119, 119, 119, 119, 119,
119, 119, 119, 119, 119, 119, 119, 119, 119, 119,
119, 119, 119, 119, 119, 119, 119, 119, 119, 119,
119, 119, 119, 119, 119, 119, 119, 119, 119, 119,
70, 70, 70, 70, 110, 57, 110, 23, 57, 112,
70, 70, 70, 70, 90, 57, 25, 23, 57, 112,
57, 112, 23, 23, 23, 23, 23, 23, 23, 23,
23, 23, 23, 23, 23, 23, 23, 23, 23, 23,
23, 23, 23, 23, 23, 23, 23, 23, 23, 23,
23, 23, 23, 23, 23, 23, 23, 23, 23, 23,
23, 23, 109, 109, 109, 109, 109, 109, 90, 93,
36, 36, 109, 109, 109, 109, 109, 109, 109, 109,
109, 109, 109, 12, 109, 47, 47, 47, 47, 47,
23, 23, 109, 109, 109, 109, 109, 109, 65, 36,
36, 93, 109, 109, 109, 109, 109, 109, 109, 109,
109, 109, 109, 5, 109, 47, 47, 47, 47, 47,
47, 117, 117, 10, 10, 47, 47, 47, 47, 47,
47, 47, 47, 47, 47, 10, 115, 10, 115, 10,
5, 10, 10, 10, 53, 65, 53, 53, 53, 53,
47, 47, 47, 47, 47, 10, 110, 10, 110, 10,
46, 10, 10, 10, 53, 29, 53, 53, 53, 53,
53, 53, 53, 53, 53, 53, 53, 53, 53, 53,
53, 53, 53, 53, 53, 53, 53, 53, 53, 53,
53, 53, 53, 53, 53, 53, 53, 53, 53, 53,
53, 53, 53, 53, 53, 53, 53, 53, 53, 102,
102, 102, 102, 102, 102, 102, 102, 102, 102, 46,
2, 40, 8, 2, 29, 40, 8, 8, 8, 7,
7, 7, 7, 8, 75, 7, 7, 7, 40, 40,
134, 134, 77, 53, 53, 53, 53, 53, 53, 8,
8, 134, 75, 53, 53, 53, 53, 53, 53, 53,
53, 53, 53, 53, 101, 53, 53, 53, 53, 53,
53, 28, 132, 28, 28, 53, 28, 28, 28, 28,
28, 28, 28, 28, 28, 28, 28, 28, 28, 19,
43, 53, 53, 53, 53, 120, 30, 53, 53, 53,
56, 56, 56, 30, 30, 30, 30, 8, 8, 8,
8, 8, 8, 124, 8, 45, 19, 19, 130, 44,
30, 116, 116, 116, 75, 75, 94, 8, 75, 111,
72, 111, 30, 133, 133, 14, 8, 30, 111, 111,
111, 75, 75, 75, 133, 116, 54, 63, 63, 63,
54, 75, 116, 74, 54, 61, 61, 61, 133, 118,
118, 78, 111, 93, 18, 9, 21, 13, 67, 105,
54, 20, 17, 107, 19, 19, 19, 19, 19, 19,
54, 19, 59, 22, 60, 79, 65, 65, 64, 123,
100, 92, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, 90, -1, -1, -1,
102, 102, 102, 102, 102, 102, 102, 102, 102, 40,
118, 118, 8, 40, 134, 134, 8, 8, 8, 56,
56, 56, 115, 8, 115, 75, 40, 40, 77, 134,
63, 63, 63, 53, 53, 53, 53, 53, 53, 8,
8, 101, 75, 53, 53, 53, 53, 53, 53, 53,
53, 53, 53, 53, 132, 53, 53, 53, 53, 53,
53, 43, 120, 28, 19, 28, 28, 53, 28, 28,
28, 28, 28, 28, 28, 28, 28, 28, 28, 28,
28, 53, 53, 53, 53, 124, 30, 45, 53, 53,
53, 19, 19, 30, 30, 30, 30, 8, 8, 8,
8, 8, 8, 130, 8, 7, 7, 7, 7, 44,
30, 7, 7, 7, 75, 75, 72, 8, 75, 61,
61, 61, 30, 133, 133, 2, 8, 30, 2, 65,
65, 75, 75, 75, 111, 54, 111, 94, 133, 54,
74, 75, 90, 54, 14, 111, 111, 111, 133, 19,
19, 19, 19, 19, 19, 93, 19, 18, 13, 54,
116, 116, 116, 21, 9, 20, 67, 111, 78, 54,
17, 105, 107, 59, 22, 60, 79, 64, 123, 100,
92, -1, 116, -1, -1, -1, -1, -1, -1, 116,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
@ -756,24 +756,26 @@ class Php5 extends \PhpParser\ParserAbstract
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, 93
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, 93
);
protected $gotoBase = array(
0, 0, -280, 0, 0, 288, 0, 366, 42, 174,
282, 154, 271, 160, 155, 0, 0, 106, 179, 109,
163, 180, 83, 7, 0, 103, 0, 0, -228, 346,
23, 0, 0, 0, 0, 0, 224, 0, 0, -22,
339, 0, 0, 392, 169, 175, 343, -4, 0, 0,
0, 0, 0, 104, 62, 0, 137, -251, 0, 86,
78, -168, 0, 159, 81, -134, 0, 151, 0, 0,
-79, 0, 164, 0, 182, 38, 0, 359, 145, 84,
0, 0, -165, 0, 0, 261, 0, 452, 42, 183,
282, 109, 154, 161, 174, 0, 0, 114, 182, 94,
167, 187, 84, 7, 0, 198, 0, 0, -226, 287,
23, 0, 0, 0, 0, 0, 223, 0, 0, -22,
337, 0, 0, 373, 169, 157, 284, -4, 0, 0,
0, 0, 0, 104, 61, 0, 66, -251, 0, 87,
79, -194, 0, 52, 80, -181, 0, 159, 0, 0,
-79, 0, 160, 0, 177, 38, 0, 355, 162, 85,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
246, 0, 71, 220, 165, 0, 0, 0, 0, 0,
73, 379, 307, 0, 0, 93, 0, 94, 0, -27,
-95, 170, -90, 0, 0, -3, 153, -28, 178, -45,
232, 0, 0, 77, 249, 0, 0, 0, 0, 0,
176, 0, 385, 161, 68, 0
192, 0, 70, 222, 186, 0, 0, 0, 0, 0,
72, 366, 307, 0, 0, 105, 0, 103, 0, -27,
-3, 185, -90, 0, 0, 73, 200, -30, 39, -45,
209, 0, 0, 76, 231, 0, 0, 0, 0, 0,
171, 0, 377, 165, 56, 0, 0
);
protected $gotoDefault = array(
@ -788,9 +790,9 @@ class Php5 extends \PhpParser\ParserAbstract
916, 418, 401, 588, 920, 921, 5, 925, 626, 627,
940, 300, 948, 961, 416, 1029, 1031, 484, 485, 525,
456, 507, 530, 486, 1052, 436, 403, 1055, 487, 488,
426, 427, 1073, 1070, 346, 1154, 345, 444, 312, 1141,
584, 1105, 452, 1193, 1150, 336, 489, 490, 359, 376,
1188, 431, 1195, 1202, 337, 571
426, 427, 1073, 1070, 346, 1154, 345, 444, 308, 1141,
584, 1105, 452, 1193, 1150, 337, 489, 490, 359, 376,
1188, 431, 1195, 1202, 333, 367, 571
);
protected $ruleToNonTerminal = array(
@ -847,8 +849,8 @@ class Php5 extends \PhpParser\ParserAbstract
111, 115, 115, 110, 110, 128, 128, 128, 128, 116,
116, 116, 116, 102, 102, 117, 117, 117, 117, 70,
129, 129, 130, 130, 130, 101, 101, 131, 131, 132,
132, 132, 132, 118, 118, 118, 118, 134, 133, 133,
133, 133, 133, 133, 133, 135, 135, 135
132, 132, 132, 118, 118, 118, 118, 134, 135, 133,
133, 133, 133, 133, 133, 133, 136, 136, 136
);
protected $ruleToLength = array(
@ -905,8 +907,8 @@ class Php5 extends \PhpParser\ParserAbstract
2, 1, 1, 4, 1, 3, 6, 4, 4, 4,
4, 1, 4, 0, 1, 1, 3, 1, 1, 4,
3, 1, 1, 1, 0, 0, 2, 3, 1, 3,
1, 4, 2, 2, 2, 1, 2, 1, 1, 4,
3, 3, 3, 6, 3, 1, 1, 1
1, 4, 2, 2, 2, 1, 2, 1, 1, 1,
4, 3, 3, 3, 6, 3, 1, 1, 1
);
protected function reduceRule0() {
@ -1267,15 +1269,15 @@ class Php5 extends \PhpParser\ParserAbstract
}
protected function reduceRule89() {
$this->semValue = new Stmt\Namespace_($this->semStack[$this->stackPos-(3-2)], null, $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
$this->semValue = new Stmt\Namespace_($this->semStack[$this->stackPos-(3-2)], null, $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes); $this->checkNamespace($this->semValue);
}
protected function reduceRule90() {
$this->semValue = new Stmt\Namespace_($this->semStack[$this->stackPos-(5-2)], $this->semStack[$this->stackPos-(5-4)], $this->startAttributeStack[$this->stackPos-(5-1)] + $this->endAttributes);
$this->semValue = new Stmt\Namespace_($this->semStack[$this->stackPos-(5-2)], $this->semStack[$this->stackPos-(5-4)], $this->startAttributeStack[$this->stackPos-(5-1)] + $this->endAttributes); $this->checkNamespace($this->semValue);
}
protected function reduceRule91() {
$this->semValue = new Stmt\Namespace_(null, $this->semStack[$this->stackPos-(4-3)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
$this->semValue = new Stmt\Namespace_(null, $this->semStack[$this->stackPos-(4-3)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes); $this->checkNamespace($this->semValue);
}
protected function reduceRule92() {
@ -1303,19 +1305,19 @@ class Php5 extends \PhpParser\ParserAbstract
}
protected function reduceRule98() {
$this->semValue = new Stmt\GroupUse(new Name($this->semStack[$this->stackPos-(7-3)], $this->startAttributeStack[$this->stackPos-(7-1)] + $this->endAttributes), $this->semStack[$this->stackPos-(7-6)], $this->semStack[$this->stackPos-(7-2)], $this->startAttributeStack[$this->stackPos-(7-1)] + $this->endAttributes);
$this->semValue = new Stmt\GroupUse(new Name($this->semStack[$this->stackPos-(7-3)], $this->startAttributeStack[$this->stackPos-(7-3)] + $this->endAttributeStack[$this->stackPos-(7-3)]), $this->semStack[$this->stackPos-(7-6)], $this->semStack[$this->stackPos-(7-2)], $this->startAttributeStack[$this->stackPos-(7-1)] + $this->endAttributes);
}
protected function reduceRule99() {
$this->semValue = new Stmt\GroupUse(new Name($this->semStack[$this->stackPos-(8-4)], $this->startAttributeStack[$this->stackPos-(8-1)] + $this->endAttributes), $this->semStack[$this->stackPos-(8-7)], $this->semStack[$this->stackPos-(8-2)], $this->startAttributeStack[$this->stackPos-(8-1)] + $this->endAttributes);
$this->semValue = new Stmt\GroupUse(new Name($this->semStack[$this->stackPos-(8-4)], $this->startAttributeStack[$this->stackPos-(8-4)] + $this->endAttributeStack[$this->stackPos-(8-4)]), $this->semStack[$this->stackPos-(8-7)], $this->semStack[$this->stackPos-(8-2)], $this->startAttributeStack[$this->stackPos-(8-1)] + $this->endAttributes);
}
protected function reduceRule100() {
$this->semValue = new Stmt\GroupUse(new Name($this->semStack[$this->stackPos-(6-2)], $this->startAttributeStack[$this->stackPos-(6-1)] + $this->endAttributes), $this->semStack[$this->stackPos-(6-5)], Stmt\Use_::TYPE_UNKNOWN, $this->startAttributeStack[$this->stackPos-(6-1)] + $this->endAttributes);
$this->semValue = new Stmt\GroupUse(new Name($this->semStack[$this->stackPos-(6-2)], $this->startAttributeStack[$this->stackPos-(6-2)] + $this->endAttributeStack[$this->stackPos-(6-2)]), $this->semStack[$this->stackPos-(6-5)], Stmt\Use_::TYPE_UNKNOWN, $this->startAttributeStack[$this->stackPos-(6-1)] + $this->endAttributes);
}
protected function reduceRule101() {
$this->semValue = new Stmt\GroupUse(new Name($this->semStack[$this->stackPos-(7-3)], $this->startAttributeStack[$this->stackPos-(7-1)] + $this->endAttributes), $this->semStack[$this->stackPos-(7-6)], Stmt\Use_::TYPE_UNKNOWN, $this->startAttributeStack[$this->stackPos-(7-1)] + $this->endAttributes);
$this->semValue = new Stmt\GroupUse(new Name($this->semStack[$this->stackPos-(7-3)], $this->startAttributeStack[$this->stackPos-(7-3)] + $this->endAttributeStack[$this->stackPos-(7-3)]), $this->semStack[$this->stackPos-(7-6)], Stmt\Use_::TYPE_UNKNOWN, $this->startAttributeStack[$this->stackPos-(7-1)] + $this->endAttributes);
}
protected function reduceRule102() {
@ -1343,11 +1345,11 @@ class Php5 extends \PhpParser\ParserAbstract
}
protected function reduceRule108() {
$this->semValue = new Stmt\UseUse($this->semStack[$this->stackPos-(1-1)], null, Stmt\Use_::TYPE_UNKNOWN, $this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
$this->semValue = new Stmt\UseUse($this->semStack[$this->stackPos-(1-1)], null, Stmt\Use_::TYPE_UNKNOWN, $this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes); $this->checkUseUse($this->semValue, $this->stackPos-(1-1));
}
protected function reduceRule109() {
$this->semValue = new Stmt\UseUse($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], Stmt\Use_::TYPE_UNKNOWN, $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
$this->semValue = new Stmt\UseUse($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], Stmt\Use_::TYPE_UNKNOWN, $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes); $this->checkUseUse($this->semValue, $this->stackPos-(3-3));
}
protected function reduceRule110() {
@ -1512,7 +1514,7 @@ class Php5 extends \PhpParser\ParserAbstract
}
protected function reduceRule150() {
$this->semValue = new Stmt\TryCatch($this->semStack[$this->stackPos-(6-3)], $this->semStack[$this->stackPos-(6-5)], $this->semStack[$this->stackPos-(6-6)], $this->startAttributeStack[$this->stackPos-(6-1)] + $this->endAttributes);
$this->semValue = new Stmt\TryCatch($this->semStack[$this->stackPos-(6-3)], $this->semStack[$this->stackPos-(6-5)], $this->semStack[$this->stackPos-(6-6)], $this->startAttributeStack[$this->stackPos-(6-1)] + $this->endAttributes); $this->checkTryCatch($this->semValue);
}
protected function reduceRule151() {
@ -1594,10 +1596,12 @@ class Php5 extends \PhpParser\ParserAbstract
protected function reduceRule170() {
$this->semValue = new Stmt\Class_($this->semStack[$this->stackPos-(7-2)], ['type' => $this->semStack[$this->stackPos-(7-1)], 'extends' => $this->semStack[$this->stackPos-(7-3)], 'implements' => $this->semStack[$this->stackPos-(7-4)], 'stmts' => $this->semStack[$this->stackPos-(7-6)]], $this->startAttributeStack[$this->stackPos-(7-1)] + $this->endAttributes);
$this->checkClass($this->semValue, $this->stackPos-(7-2));
}
protected function reduceRule171() {
$this->semValue = new Stmt\Interface_($this->semStack[$this->stackPos-(6-2)], ['extends' => $this->semStack[$this->stackPos-(6-3)], 'stmts' => $this->semStack[$this->stackPos-(6-5)]], $this->startAttributeStack[$this->stackPos-(6-1)] + $this->endAttributes);
$this->checkInterface($this->semValue, $this->stackPos-(6-2));
}
protected function reduceRule172() {
@ -1805,11 +1809,11 @@ class Php5 extends \PhpParser\ParserAbstract
}
protected function reduceRule223() {
$this->semValue = new Node\Param(substr($this->semStack[$this->stackPos-(4-4)], 1), null, $this->semStack[$this->stackPos-(4-1)], $this->semStack[$this->stackPos-(4-2)], $this->semStack[$this->stackPos-(4-3)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
$this->semValue = new Node\Param(substr($this->semStack[$this->stackPos-(4-4)], 1), null, $this->semStack[$this->stackPos-(4-1)], $this->semStack[$this->stackPos-(4-2)], $this->semStack[$this->stackPos-(4-3)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes); $this->checkParam($this->semValue);
}
protected function reduceRule224() {
$this->semValue = new Node\Param(substr($this->semStack[$this->stackPos-(6-4)], 1), $this->semStack[$this->stackPos-(6-6)], $this->semStack[$this->stackPos-(6-1)], $this->semStack[$this->stackPos-(6-2)], $this->semStack[$this->stackPos-(6-3)], $this->startAttributeStack[$this->stackPos-(6-1)] + $this->endAttributes);
$this->semValue = new Node\Param(substr($this->semStack[$this->stackPos-(6-4)], 1), $this->semStack[$this->stackPos-(6-6)], $this->semStack[$this->stackPos-(6-1)], $this->semStack[$this->stackPos-(6-2)], $this->semStack[$this->stackPos-(6-3)], $this->startAttributeStack[$this->stackPos-(6-1)] + $this->endAttributes); $this->checkParam($this->semValue);
}
protected function reduceRule225() {
@ -1917,7 +1921,7 @@ class Php5 extends \PhpParser\ParserAbstract
}
protected function reduceRule251() {
$this->semValue = new Stmt\Property($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-2)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
$this->semValue = new Stmt\Property($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-2)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes); $this->checkProperty($this->semValue, $this->stackPos-(3-1));
}
protected function reduceRule252() {
@ -1926,6 +1930,7 @@ class Php5 extends \PhpParser\ParserAbstract
protected function reduceRule253() {
$this->semValue = new Stmt\ClassMethod($this->semStack[$this->stackPos-(9-4)], ['type' => $this->semStack[$this->stackPos-(9-1)], 'byRef' => $this->semStack[$this->stackPos-(9-3)], 'params' => $this->semStack[$this->stackPos-(9-6)], 'returnType' => $this->semStack[$this->stackPos-(9-8)], 'stmts' => $this->semStack[$this->stackPos-(9-9)]], $this->startAttributeStack[$this->stackPos-(9-1)] + $this->endAttributes);
$this->checkClassMethod($this->semValue, $this->stackPos-(9-1));
}
protected function reduceRule254() {
@ -2009,7 +2014,7 @@ class Php5 extends \PhpParser\ParserAbstract
}
protected function reduceRule274() {
Stmt\Class_::verifyModifier($this->semStack[$this->stackPos-(2-1)], $this->semStack[$this->stackPos-(2-2)]); $this->semValue = $this->semStack[$this->stackPos-(2-1)] | $this->semStack[$this->stackPos-(2-2)];
$this->checkModifier($this->semStack[$this->stackPos-(2-1)], $this->semStack[$this->stackPos-(2-2)], $this->stackPos-(2-2)); $this->semValue = $this->semStack[$this->stackPos-(2-1)] | $this->semStack[$this->stackPos-(2-2)];
}
protected function reduceRule275() {
@ -2451,6 +2456,7 @@ class Php5 extends \PhpParser\ParserAbstract
protected function reduceRule383() {
$this->semValue = array(new Stmt\Class_(null, ['type' => 0, 'extends' => $this->semStack[$this->stackPos-(7-3)], 'implements' => $this->semStack[$this->stackPos-(7-4)], 'stmts' => $this->semStack[$this->stackPos-(7-6)]], $this->startAttributeStack[$this->stackPos-(7-1)] + $this->endAttributes), $this->semStack[$this->stackPos-(7-2)]);
$this->checkClass($this->semValue[0], -1);
}
protected function reduceRule384() {
@ -2612,7 +2618,7 @@ class Php5 extends \PhpParser\ParserAbstract
}
protected function reduceRule420() {
$this->semValue = Scalar\LNumber::fromString($this->semStack[$this->stackPos-(1-1)], $this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes, true);
$this->semValue = $this->parseLNumber($this->semStack[$this->stackPos-(1-1)], $this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes, true);
}
protected function reduceRule421() {
@ -3029,7 +3035,7 @@ class Php5 extends \PhpParser\ParserAbstract
}
protected function reduceRule523() {
$this->semValue = $this->semStack[$this->stackPos-(1-1)];
$this->semValue = new Expr\ArrayItem($this->semStack[$this->stackPos-(1-1)], null, false, $this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
}
protected function reduceRule524() {
@ -3093,15 +3099,15 @@ class Php5 extends \PhpParser\ParserAbstract
}
protected function reduceRule539() {
$this->semValue = new Expr\ArrayDimFetch(new Expr\Variable(substr($this->semStack[$this->stackPos-(4-1)], 1), $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes), $this->semStack[$this->stackPos-(4-3)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
$this->semValue = $this->semStack[$this->stackPos-(1-1)];
}
protected function reduceRule540() {
$this->semValue = new Expr\PropertyFetch(new Expr\Variable(substr($this->semStack[$this->stackPos-(3-1)], 1), $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes), $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
$this->semValue = new Expr\ArrayDimFetch($this->semStack[$this->stackPos-(4-1)], $this->semStack[$this->stackPos-(4-3)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
}
protected function reduceRule541() {
$this->semValue = new Expr\Variable($this->semStack[$this->stackPos-(3-2)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
$this->semValue = new Expr\PropertyFetch($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
}
protected function reduceRule542() {
@ -3109,15 +3115,15 @@ class Php5 extends \PhpParser\ParserAbstract
}
protected function reduceRule543() {
$this->semValue = new Expr\ArrayDimFetch(new Expr\Variable($this->semStack[$this->stackPos-(6-2)], $this->startAttributeStack[$this->stackPos-(6-1)] + $this->endAttributes), $this->semStack[$this->stackPos-(6-4)], $this->startAttributeStack[$this->stackPos-(6-1)] + $this->endAttributes);
$this->semValue = new Expr\Variable($this->semStack[$this->stackPos-(3-2)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
}
protected function reduceRule544() {
$this->semValue = $this->semStack[$this->stackPos-(3-2)];
$this->semValue = new Expr\ArrayDimFetch(new Expr\Variable($this->semStack[$this->stackPos-(6-2)], $this->startAttributeStack[$this->stackPos-(6-1)] + $this->endAttributes), $this->semStack[$this->stackPos-(6-4)], $this->startAttributeStack[$this->stackPos-(6-1)] + $this->endAttributes);
}
protected function reduceRule545() {
$this->semValue = new Scalar\String_($this->semStack[$this->stackPos-(1-1)], $this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
$this->semValue = $this->semStack[$this->stackPos-(3-2)];
}
protected function reduceRule546() {
@ -3125,6 +3131,10 @@ class Php5 extends \PhpParser\ParserAbstract
}
protected function reduceRule547() {
$this->semValue = $this->parseNumString($this->semStack[$this->stackPos-(1-1)], $this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
}
protected function reduceRule548() {
$this->semValue = new Expr\Variable(substr($this->semStack[$this->stackPos-(1-1)], 1), $this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
}
}

File diff suppressed because it is too large Load Diff

View File

@ -7,6 +7,17 @@ namespace PhpParser;
* turn is based on work by Masato Bito.
*/
use PhpParser\Node\Name;
use PhpParser\Node\Param;
use PhpParser\Node\Scalar\LNumber;
use PhpParser\Node\Scalar\String_;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\ClassConst;
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\Node\Stmt\Interface_;
use PhpParser\Node\Stmt\Namespace_;
use PhpParser\Node\Stmt\Property;
use PhpParser\Node\Stmt\TryCatch;
use PhpParser\Node\Stmt\UseUse;
abstract class ParserAbstract implements Parser
{
@ -86,13 +97,15 @@ abstract class ParserAbstract implements Parser
protected $semStack;
/** @var array[] Start attribute stack */
protected $startAttributeStack;
/** @var array[] End attribute stack */
protected $endAttributeStack;
/** @var array End attributes of last *shifted* token */
protected $endAttributes;
/** @var array Start attributes of last *read* token */
protected $lookaheadStartAttributes;
/** @var bool Whether to throw on first error */
protected $throwOnError;
/** @var ErrorHandler Error handler */
protected $errorHandler;
/** @var Error[] Errors collected during last parse */
protected $errors;
/** @var int Error state, used to avoid error floods */
@ -102,38 +115,36 @@ abstract class ParserAbstract implements Parser
* Creates a parser instance.
*
* @param Lexer $lexer A lexer
* @param array $options Options array. The boolean 'throwOnError' option determines whether an exception should be
* thrown on first error, or if the parser should try to continue parsing the remaining code
* and build a partial AST.
* @param array $options Options array. Currently no options are supported.
*/
public function __construct(Lexer $lexer, array $options = array()) {
$this->lexer = $lexer;
$this->errors = array();
$this->throwOnError = isset($options['throwOnError']) ? $options['throwOnError'] : true;
}
/**
* Get array of errors that occurred during the last parse.
*
* This method may only return multiple errors if the 'throwOnError' option is disabled.
*
* @return Error[]
*/
public function getErrors() {
return $this->errors;
if (isset($options['throwOnError'])) {
throw new \LogicException(
'"throwOnError" is no longer supported, use "errorHandler" instead');
}
}
/**
* Parses PHP code into a node tree.
*
* If a non-throwing error handler is used, the parser will continue parsing after an error
* occurred and attempt to build a partial AST.
*
* @param string $code The source code to parse
* @param ErrorHandler|null $errorHandler Error handler to use for lexer/parser errors, defaults
* to ErrorHandler\Throwing.
*
* @return Node[]|null Array of statements (or null if the 'throwOnError' option is disabled and the parser was
* unable to recover from an error).
*/
public function parse($code) {
$this->lexer->startLexing($code);
$this->errors = array();
public function parse($code, ErrorHandler $errorHandler = null) {
$this->errorHandler = $errorHandler ?: new ErrorHandler\Throwing;
// Initialize the lexer
$this->lexer->startLexing($code, $this->errorHandler);
// We start off with no lookahead-token
$symbol = self::SYMBOL_NONE;
@ -145,9 +156,9 @@ abstract class ParserAbstract implements Parser
$endAttributes = '*POISON';
$this->endAttributes = $endAttributes;
// In order to figure out the attributes for the starting token, we have to keep
// them in a stack
// Keep stack of start and end attributes
$this->startAttributeStack = array();
$this->endAttributeStack = array($endAttributes);
// Start off in the initial state and keep a stack of previous states
$state = 0;
@ -189,6 +200,7 @@ abstract class ParserAbstract implements Parser
// This is necessary to assign some meaningful attributes to /* empty */ productions. They'll get
// the attributes of the next token, even though they don't contain it themselves.
$this->startAttributeStack[$this->stackPos+1] = $startAttributes;
$this->endAttributeStack[$this->stackPos+1] = $endAttributes;
$this->lookaheadStartAttributes = $startAttributes;
//$this->traceRead($symbol);
@ -215,6 +227,7 @@ abstract class ParserAbstract implements Parser
$stateStack[$this->stackPos] = $state = $action;
$this->semStack[$this->stackPos] = $tokenValue;
$this->startAttributeStack[$this->stackPos] = $startAttributes;
$this->endAttributeStack[$this->stackPos] = $endAttributes;
$this->endAttributes = $endAttributes;
$symbol = self::SYMBOL_NONE;
@ -252,16 +265,13 @@ abstract class ParserAbstract implements Parser
$e->setStartLine($startAttributes['startLine']);
}
$this->errors[] = $e;
if ($this->throwOnError) {
throw $e;
} else {
// Currently can't recover from "special" errors
return null;
}
$this->emitError($e);
// Can't recover from this type of error
return null;
}
/* Goto - shift nonterminal */
$lastEndAttributes = $this->endAttributeStack[$this->stackPos];
$this->stackPos -= $this->ruleToLength[$rule];
$nonTerminal = $this->ruleToNonTerminal[$rule];
$idx = $this->gotoBase[$nonTerminal] + $stateStack[$this->stackPos];
@ -274,16 +284,13 @@ abstract class ParserAbstract implements Parser
++$this->stackPos;
$stateStack[$this->stackPos] = $state;
$this->semStack[$this->stackPos] = $this->semValue;
$this->endAttributeStack[$this->stackPos] = $lastEndAttributes;
} else {
/* error */
switch ($this->errorState) {
case 0:
$msg = $this->getErrorMessage($symbol, $state);
$error = new Error($msg, $startAttributes + $endAttributes);
$this->errors[] = $error;
if ($this->throwOnError) {
throw $error;
}
$this->emitError(new Error($msg, $startAttributes + $endAttributes));
// Break missing intentionally
case 1:
case 2:
@ -306,7 +313,9 @@ abstract class ParserAbstract implements Parser
}
//$this->traceShift($this->errorSymbol);
$stateStack[++$this->stackPos] = $state = $action;
++$this->stackPos;
$stateStack[$this->stackPos] = $state = $action;
$this->endAttributes = $this->endAttributeStack[$this->stackPos];
break;
case 3:
@ -333,6 +342,10 @@ abstract class ParserAbstract implements Parser
throw new \RuntimeException('Reached end of parser loop');
}
protected function emitError(Error $error) {
$this->errorHandler->handleError($error);
}
protected function getErrorMessage($symbol, $state) {
$expectedString = '';
if ($expected = $this->getExpectedTokens($state)) {
@ -355,6 +368,7 @@ abstract class ParserAbstract implements Parser
) {
if ($this->action[$idx] != $this->unexpectedTokenRule
&& $this->action[$idx] != $this->defaultAction
&& $symbol != $this->errorSymbol
) {
if (count($expected) == 4) {
/* Too many expected tokens */
@ -415,6 +429,7 @@ abstract class ParserAbstract implements Parser
* @return Node[]
*/
protected function handleNamespaces(array $stmts) {
$hasErrored = false;
$style = $this->getNamespacingStyle($stmts);
if (null === $style) {
// not namespaced, nothing to do
@ -425,8 +440,11 @@ abstract class ParserAbstract implements Parser
foreach ($stmts as $stmt) {
if ($stmt instanceof Node\Stmt\Namespace_) {
$afterFirstNamespace = true;
} elseif (!$stmt instanceof Node\Stmt\HaltCompiler && $afterFirstNamespace) {
throw new Error('No code may exist outside of namespace {}', $stmt->getLine());
} elseif (!$stmt instanceof Node\Stmt\HaltCompiler
&& $afterFirstNamespace && !$hasErrored) {
$this->emitError(new Error(
'No code may exist outside of namespace {}', $stmt->getAttributes()));
$hasErrored = true; // Avoid one error for every statement
}
}
return $stmts;
@ -436,9 +454,15 @@ abstract class ParserAbstract implements Parser
$targetStmts =& $resultStmts;
foreach ($stmts as $stmt) {
if ($stmt instanceof Node\Stmt\Namespace_) {
$stmt->stmts = array();
$targetStmts =& $stmt->stmts;
$resultStmts[] = $stmt;
if ($stmt->stmts === null) {
$stmt->stmts = array();
$targetStmts =& $stmt->stmts;
$resultStmts[] = $stmt;
} else {
// This handles the invalid case of mixed style namespaces
$resultStmts[] = $stmt;
$targetStmts =& $resultStmts;
}
} elseif ($stmt instanceof Node\Stmt\HaltCompiler) {
// __halt_compiler() is not moved into the namespace
$resultStmts[] = $stmt;
@ -459,10 +483,18 @@ abstract class ParserAbstract implements Parser
if (null === $style) {
$style = $currentStyle;
if ($hasNotAllowedStmts) {
throw new Error('Namespace declaration statement has to be the very first statement in the script', $stmt->getLine());
$this->emitError(new Error(
'Namespace declaration statement has to be the very first statement in the script',
$stmt->getLine() // Avoid marking the entire namespace as an error
));
}
} elseif ($style !== $currentStyle) {
throw new Error('Cannot mix bracketed namespace declarations with unbracketed namespace declarations', $stmt->getLine());
$this->emitError(new Error(
'Cannot mix bracketed namespace declarations with unbracketed namespace declarations',
$stmt->getLine() // Avoid marking the entire namespace as an error
));
// Treat like semicolon style for namespace normalization
return 'semicolon';
}
continue;
}
@ -502,4 +534,190 @@ abstract class ParserAbstract implements Parser
$lowerName = strtolower($name->toString());
return isset($scalarTypes[$lowerName]) ? $lowerName : $name;
}
protected static $specialNames = array(
'self' => true,
'parent' => true,
'static' => true,
);
protected function getAttributesAt($pos) {
return $this->startAttributeStack[$pos] + $this->endAttributeStack[$pos];
}
protected function parseLNumber($str, $attributes, $allowInvalidOctal = false) {
try {
return LNumber::fromString($str, $attributes, $allowInvalidOctal);
} catch (Error $error) {
$this->emitError($error);
// Use dummy value
return new LNumber(0, $attributes);
}
}
protected function parseNumString($str, $attributes) {
if (!preg_match('/^(?:0|-?[1-9][0-9]*)$/', $str)) {
return new String_($str, $attributes);
}
$num = +$str;
if (!is_int($num)) {
return new String_($str, $attributes);
}
return new LNumber($num, $attributes);
}
protected function checkModifier($a, $b, $modifierPos) {
// Jumping through some hoops here because verifyModifier() is also used elsewhere
try {
Class_::verifyModifier($a, $b);
} catch (Error $error) {
$error->setAttributes($this->getAttributesAt($modifierPos));
$this->emitError($error);
}
}
protected function checkParam(Param $node) {
if ($node->variadic && null !== $node->default) {
$this->emitError(new Error(
'Variadic parameter cannot have a default value',
$node->default->getAttributes()
));
}
}
protected function checkTryCatch(TryCatch $node) {
if (empty($node->catches) && null === $node->finally) {
$this->emitError(new Error(
'Cannot use try without catch or finally', $node->getAttributes()
));
}
}
protected function checkNamespace(Namespace_ $node) {
if (isset(self::$specialNames[strtolower($node->name)])) {
$this->emitError(new Error(
sprintf('Cannot use \'%s\' as namespace name', $node->name),
$node->name->getAttributes()
));
}
if (null !== $node->stmts) {
foreach ($node->stmts as $stmt) {
if ($stmt instanceof Namespace_) {
$this->emitError(new Error(
'Namespace declarations cannot be nested', $stmt->getAttributes()
));
}
}
}
}
protected function checkClass(Class_ $node, $namePos) {
if (null !== $node->name && isset(self::$specialNames[strtolower($node->name)])) {
$this->emitError(new Error(
sprintf('Cannot use \'%s\' as class name as it is reserved', $node->name),
$this->getAttributesAt($namePos)
));
}
if (isset(self::$specialNames[strtolower($node->extends)])) {
$this->emitError(new Error(
sprintf('Cannot use \'%s\' as class name as it is reserved', $node->extends),
$node->extends->getAttributes()
));
}
foreach ($node->implements as $interface) {
if (isset(self::$specialNames[strtolower($interface)])) {
$this->emitError(new Error(
sprintf('Cannot use \'%s\' as interface name as it is reserved', $interface),
$interface->getAttributes()
));
}
}
}
protected function checkInterface(Interface_ $node, $namePos) {
if (null !== $node->name && isset(self::$specialNames[strtolower($node->name)])) {
$this->emitError(new Error(
sprintf('Cannot use \'%s\' as class name as it is reserved', $node->name),
$this->getAttributesAt($namePos)
));
}
foreach ($node->extends as $interface) {
if (isset(self::$specialNames[strtolower($interface)])) {
$this->emitError(new Error(
sprintf('Cannot use \'%s\' as interface name as it is reserved', $interface),
$interface->getAttributes()
));
}
}
}
protected function checkClassMethod(ClassMethod $node, $modifierPos) {
if ($node->flags & Class_::MODIFIER_STATIC) {
switch (strtolower($node->name)) {
case '__construct':
$this->emitError(new Error(
sprintf('Constructor %s() cannot be static', $node->name),
$this->getAttributesAt($modifierPos)));
break;
case '__destruct':
$this->emitError(new Error(
sprintf('Destructor %s() cannot be static', $node->name),
$this->getAttributesAt($modifierPos)));
break;
case '__clone':
$this->emitError(new Error(
sprintf('Clone method %s() cannot be static', $node->name),
$this->getAttributesAt($modifierPos)));
break;
}
}
}
protected function checkClassConst(ClassConst $node, $modifierPos) {
if ($node->flags & Class_::MODIFIER_STATIC) {
$this->emitError(new Error(
"Cannot use 'static' as constant modifier",
$this->getAttributesAt($modifierPos)));
}
if ($node->flags & Class_::MODIFIER_ABSTRACT) {
$this->emitError(new Error(
"Cannot use 'abstract' as constant modifier",
$this->getAttributesAt($modifierPos)));
}
if ($node->flags & Class_::MODIFIER_FINAL) {
$this->emitError(new Error(
"Cannot use 'final' as constant modifier",
$this->getAttributesAt($modifierPos)));
}
}
protected function checkProperty(Property $node, $modifierPos) {
if ($node->flags & Class_::MODIFIER_ABSTRACT) {
$this->emitError(new Error('Properties cannot be declared abstract',
$this->getAttributesAt($modifierPos)));
}
if ($node->flags & Class_::MODIFIER_FINAL) {
$this->emitError(new Error('Properties cannot be declared final',
$this->getAttributesAt($modifierPos)));
}
}
protected function checkUseUse(UseUse $node, $namePos) {
if ('self' == strtolower($node->alias) || 'parent' == strtolower($node->alias)) {
$this->emitError(new Error(
sprintf(
'Cannot use %s as %s because \'%2$s\' is a special class name',
$node->name, $node->alias
),
$this->getAttributesAt($namePos)
));
}
}
}

View File

@ -2,16 +2,16 @@
namespace PhpParser\PrettyPrinter;
use PhpParser\PrettyPrinterAbstract;
use PhpParser\Node;
use PhpParser\Node\Scalar;
use PhpParser\Node\Scalar\MagicConst;
use PhpParser\Node\Expr;
use PhpParser\Node\Expr\AssignOp;
use PhpParser\Node\Expr\BinaryOp;
use PhpParser\Node\Expr\Cast;
use PhpParser\Node\Stmt;
use PhpParser\Node\Name;
use PhpParser\Node\Scalar;
use PhpParser\Node\Scalar\MagicConst;
use PhpParser\Node\Stmt;
use PhpParser\PrettyPrinterAbstract;
class Standard extends PrettyPrinterAbstract
{
@ -34,7 +34,7 @@ class Standard extends PrettyPrinterAbstract
}
protected function pNullableType(Node\NullableType $node) {
return '?' . $node->type;
return '?' . $this->pType($node->type);
}
// Names
@ -141,21 +141,42 @@ class Standard extends PrettyPrinterAbstract
}
protected function pScalar_LNumber(Scalar\LNumber $node) {
if ($node->value === -\PHP_INT_MAX-1) {
// PHP_INT_MIN cannot be represented as a literal,
// because the sign is not part of the literal
return '(-' . \PHP_INT_MAX . '-1)';
}
$kind = $node->getAttribute('kind', Scalar\LNumber::KIND_DEC);
if (Scalar\LNumber::KIND_DEC === $kind) {
return (string) $node->value;
}
$sign = $node->value < 0 ? '-' : '';
$str = (string) $node->value;
switch ($node->getAttribute('kind', Scalar\LNumber::KIND_DEC)) {
switch ($kind) {
case Scalar\LNumber::KIND_BIN:
return '0b' . base_convert($str, 10, 2);
return $sign . '0b' . base_convert($str, 10, 2);
case Scalar\LNumber::KIND_OCT:
return '0' . base_convert($str, 10, 8);
case Scalar\LNumber::KIND_DEC:
return $str;
return $sign . '0' . base_convert($str, 10, 8);
case Scalar\LNumber::KIND_HEX:
return '0x' . base_convert($str, 10, 16);
return $sign . '0x' . base_convert($str, 10, 16);
}
throw new \Exception('Invalid number kind');
}
protected function pScalar_DNumber(Scalar\DNumber $node) {
if (!is_finite($node->value)) {
if ($node->value === \INF) {
return '\INF';
} elseif ($node->value === -\INF) {
return '-\INF';
} else {
return '\NAN';
}
}
// Try to find a short full-precision representation
$stringValue = sprintf('%.16G', $node->value);
if ($node->value !== (double) $stringValue) {
$stringValue = sprintf('%.17G', $node->value);
@ -464,6 +485,10 @@ class Standard extends PrettyPrinterAbstract
// Other
protected function pExpr_Error(Expr\Error $node) {
throw new \LogicException('Cannot pretty-print AST with Error nodes');
}
protected function pExpr_Variable(Expr\Variable $node) {
if ($node->name instanceof Expr) {
return '${' . $this->p($node->name) . '}';
@ -497,7 +522,8 @@ class Standard extends PrettyPrinterAbstract
}
protected function pExpr_ClassConstFetch(Expr\ClassConstFetch $node) {
return $this->p($node->class) . '::' . $node->name;
return $this->p($node->class) . '::'
. (is_string($node->name) ? $node->name : $this->p($node->name));
}
protected function pExpr_PropertyFetch(Expr\PropertyFetch $node) {

View File

@ -2,6 +2,9 @@
namespace PhpParser;
/**
* @deprecated
*/
interface Serializer
{
/**

View File

@ -2,11 +2,14 @@
namespace PhpParser\Serializer;
use XMLWriter;
use PhpParser\Node;
use PhpParser\Comment;
use PhpParser\Node;
use PhpParser\Serializer;
use XMLWriter;
/**
* @deprecated
*/
class XML implements Serializer
{
protected $writer;

View File

@ -2,6 +2,9 @@
namespace PhpParser;
/**
* @deprecated
*/
interface Unserializer
{
/**

View File

@ -2,10 +2,13 @@
namespace PhpParser\Unserializer;
use XMLReader;
use DomainException;
use PhpParser\Unserializer;
use XMLReader;
/**
* @deprecated
*/
class XML implements Unserializer
{
protected $reader;

View File

@ -2,9 +2,6 @@
namespace PhpParser;
use PhpParser\Node\Expr;
use PhpParser\Node\Scalar;
/* The autoloader is already active at this point, so we only check effects here. */
class AutoloaderTest extends \PHPUnit_Framework_TestCase {

View File

@ -43,7 +43,7 @@ class ClassTest extends \PHPUnit_Framework_TestCase
$this->assertEquals(
new Stmt\Class_('Test', array(
'type' => Stmt\Class_::MODIFIER_ABSTRACT
'flags' => Stmt\Class_::MODIFIER_ABSTRACT
)),
$node
);
@ -57,7 +57,7 @@ class ClassTest extends \PHPUnit_Framework_TestCase
$this->assertEquals(
new Stmt\Class_('Test', array(
'type' => Stmt\Class_::MODIFIER_FINAL
'flags' => Stmt\Class_::MODIFIER_FINAL
)),
$node
);

View File

@ -4,9 +4,9 @@ namespace PhpParser\Builder;
use PhpParser\Comment;
use PhpParser\Node;
use PhpParser\Node\Stmt;
use PhpParser\Node\Expr\Print_;
use PhpParser\Node\Scalar\String_;
use PhpParser\Node\Stmt;
class FunctionTest extends \PHPUnit_Framework_TestCase
{

View File

@ -2,10 +2,10 @@
namespace PhpParser\Builder;
use PhpParser\Node;
use PhpParser\Node\Stmt;
use PhpParser\Node\Scalar\DNumber;
use PhpParser\Comment;
use PhpParser\Node;
use PhpParser\Node\Scalar\DNumber;
use PhpParser\Node\Stmt;
class InterfaceTest extends \PHPUnit_Framework_TestCase
{

View File

@ -2,11 +2,11 @@
namespace PhpParser\Builder;
use PhpParser\Comment;
use PhpParser\Node;
use PhpParser\Node\Expr\Print_;
use PhpParser\Node\Scalar\String_;
use PhpParser\Node\Stmt;
use PhpParser\Comment;
class MethodTest extends \PHPUnit_Framework_TestCase
{
@ -24,9 +24,9 @@ class MethodTest extends \PHPUnit_Framework_TestCase
$this->assertEquals(
new Stmt\ClassMethod('test', array(
'type' => Stmt\Class_::MODIFIER_PUBLIC
| Stmt\Class_::MODIFIER_ABSTRACT
| Stmt\Class_::MODIFIER_STATIC,
'flags' => Stmt\Class_::MODIFIER_PUBLIC
| Stmt\Class_::MODIFIER_ABSTRACT
| Stmt\Class_::MODIFIER_STATIC,
'stmts' => null,
)),
$node
@ -40,8 +40,8 @@ class MethodTest extends \PHPUnit_Framework_TestCase
$this->assertEquals(
new Stmt\ClassMethod('test', array(
'type' => Stmt\Class_::MODIFIER_PROTECTED
| Stmt\Class_::MODIFIER_FINAL
'flags' => Stmt\Class_::MODIFIER_PROTECTED
| Stmt\Class_::MODIFIER_FINAL
)),
$node
);

View File

@ -2,11 +2,11 @@
namespace PhpParser\Builder;
use PhpParser\Node\Name;
use PhpParser\Node\Stmt;
use PhpParser\Node\Expr;
use PhpParser\Node\Scalar;
use PhpParser\Comment;
use PhpParser\Node\Expr;
use PhpParser\Node\Name;
use PhpParser\Node\Scalar;
use PhpParser\Node\Stmt;
class PropertyTest extends \PHPUnit_Framework_TestCase
{

View File

@ -3,8 +3,6 @@
namespace PhpParser\Builder;
use PhpParser\Comment;
use PhpParser\Node;
use PhpParser\Node\Name;
use PhpParser\Node\Stmt;
class TraitTest extends \PHPUnit_Framework_TestCase

View File

@ -2,8 +2,6 @@
namespace PhpParser;
use PhpParser\Comment;
require_once __DIR__ . '/CodeTestAbstract.php';
class CodeParsingTest extends CodeTestAbstract
@ -11,24 +9,28 @@ class CodeParsingTest extends CodeTestAbstract
/**
* @dataProvider provideTestParse
*/
public function testParse($name, $code, $expected, $mode) {
public function testParse($name, $code, $expected, $modeLine) {
if (null !== $modeLine) {
$modes = array_fill_keys(explode(',', $modeLine), true);
} else {
$modes = [];
}
$lexer = new Lexer\Emulative(array('usedAttributes' => array(
'startLine', 'endLine', 'startFilePos', 'endFilePos', 'comments'
)));
$parser5 = new Parser\Php5($lexer, array(
'throwOnError' => false,
));
$parser7 = new Parser\Php7($lexer, array(
'throwOnError' => false,
));
$parser5 = new Parser\Php5($lexer);
$parser7 = new Parser\Php7($lexer);
$output5 = $this->getParseOutput($parser5, $code);
$output7 = $this->getParseOutput($parser7, $code);
$dumpPositions = isset($modes['positions']);
$output5 = $this->getParseOutput($parser5, $code, $dumpPositions);
$output7 = $this->getParseOutput($parser7, $code, $dumpPositions);
if ($mode === 'php5') {
if (isset($modes['php5'])) {
$this->assertSame($expected, $output5, $name);
$this->assertNotSame($expected, $output7, $name);
} else if ($mode === 'php7') {
} else if (isset($modes['php7'])) {
$this->assertNotSame($expected, $output5, $name);
$this->assertSame($expected, $output7, $name);
} else {
@ -37,18 +39,18 @@ class CodeParsingTest extends CodeTestAbstract
}
}
private function getParseOutput(Parser $parser, $code) {
$stmts = $parser->parse($code);
$errors = $parser->getErrors();
private function getParseOutput(Parser $parser, $code, $dumpPositions) {
$errors = new ErrorHandler\Collecting;
$stmts = $parser->parse($code, $errors);
$output = '';
foreach ($errors as $error) {
foreach ($errors->getErrors() as $error) {
$output .= $this->formatErrorMessage($error, $code) . "\n";
}
if (null !== $stmts) {
$dumper = new NodeDumper(['dumpComments' => true]);
$output .= $dumper->dump($stmts);
$dumper = new NodeDumper(['dumpComments' => true, 'dumpPositions' => $dumpPositions]);
$output .= $dumper->dump($stmts, $code);
}
return canonicalize($output);
@ -60,8 +62,7 @@ class CodeParsingTest extends CodeTestAbstract
private function formatErrorMessage(Error $e, $code) {
if ($e->hasColumnInfo()) {
return $e->getRawMessage() . ' from ' . $e->getStartLine() . ':' . $e->getStartColumn($code)
. ' to ' . $e->getEndLine() . ':' . $e->getEndColumn($code);
return $e->getMessageWithColumnInfo($code);
} else {
return $e->getMessage();
}

View File

@ -0,0 +1,22 @@
<?php
namespace PhpParser\ErrorHandler;
use PhpParser\Error;
class CollectingTest extends \PHPUnit_Framework_TestCase {
public function testHandleError() {
$errorHandler = new Collecting();
$this->assertFalse($errorHandler->hasErrors());
$this->assertEmpty($errorHandler->getErrors());
$errorHandler->handleError($e1 = new Error('Test 1'));
$errorHandler->handleError($e2 = new Error('Test 2'));
$this->assertTrue($errorHandler->hasErrors());
$this->assertSame([$e1, $e2], $errorHandler->getErrors());
$errorHandler->clearErrors();
$this->assertFalse($errorHandler->hasErrors());
$this->assertEmpty($errorHandler->getErrors());
}
}

View File

@ -0,0 +1,16 @@
<?php
namespace PhpParser\ErrorHandler;
use PhpParser\Error;
class ThrowingTest extends \PHPUnit_Framework_TestCase {
/**
* @expectedException \PhpParser\Error
* @expectedExceptionMessage Test
*/
public function testHandleError() {
$errorHandler = new Throwing();
$errorHandler->handleError(new Error('Test'));
}
}

View File

@ -15,7 +15,6 @@ class ErrorTest extends \PHPUnit_Framework_TestCase
$this->assertSame($attributes, $error->getAttributes());
$this->assertSame(10, $error->getStartLine());
$this->assertSame(11, $error->getEndLine());
$this->assertSame(10, $error->getRawLine());
$this->assertSame('Some error on line 10', $error->getMessage());
return $error;
@ -31,10 +30,6 @@ class ErrorTest extends \PHPUnit_Framework_TestCase
$error->setStartLine(15);
$this->assertSame(15, $error->getStartLine());
$this->assertSame('Some other error on line 15', $error->getMessage());
$error->setRawLine(17);
$this->assertSame(17, $error->getRawLine());
$this->assertSame('Some other error on line 17', $error->getMessage());
}
public function testUnknownLine() {
@ -42,7 +37,6 @@ class ErrorTest extends \PHPUnit_Framework_TestCase
$this->assertSame(-1, $error->getStartLine());
$this->assertSame(-1, $error->getEndLine());
$this->assertSame(-1, $error->getRawLine());
$this->assertSame('Some error on unknown line', $error->getMessage());
}

View File

@ -14,28 +14,36 @@ class LexerTest extends \PHPUnit_Framework_TestCase
/**
* @dataProvider provideTestError
*/
public function testError($code, $message) {
public function testError($code, $messages) {
if (defined('HHVM_VERSION')) {
$this->markTestSkipped('HHVM does not throw warnings from token_get_all()');
}
$lexer = $this->getLexer();
try {
$lexer->startLexing($code);
} catch (Error $e) {
$this->assertSame($message, $e->getMessage());
$errorHandler = new ErrorHandler\Collecting();
$lexer = $this->getLexer(['usedAttributes' => [
'comments', 'startLine', 'endLine', 'startFilePos', 'endFilePos'
]]);
$lexer->startLexing($code, $errorHandler);
$errors = $errorHandler->getErrors();
return;
$this->assertSame(count($messages), count($errors));
for ($i = 0; $i < count($messages); $i++) {
$this->assertSame($messages[$i], $errors[$i]->getMessageWithColumnInfo($code));
}
$this->fail('Expected PhpParser\Error');
}
public function provideTestError() {
return array(
array('<?php /*', 'Unterminated comment on line 1'),
array('<?php ' . "\1", 'Unexpected character "' . "\1" . '" (ASCII 1) on unknown line'),
array('<?php ' . "\0", 'Unexpected null byte on unknown line'),
array("<?php /*", array("Unterminated comment from 1:7 to 1:9")),
array("<?php \1", array("Unexpected character \"\1\" (ASCII 1) from 1:7 to 1:7")),
array("<?php \0", array("Unexpected null byte from 1:7 to 1:7")),
// Error with potentially emulated token
array("<?php ?? \0", array("Unexpected null byte from 1:10 to 1:10")),
array("<?php\n\0\1 foo /* bar", array(
"Unexpected null byte from 2:1 to 2:1",
"Unexpected character \"\1\" (ASCII 1) from 2:2 to 2:2",
"Unterminated comment from 2:8 to 2:14"
)),
);
}

View File

@ -10,6 +10,9 @@ class NameTest extends \PHPUnit_Framework_TestCase
$name = new Name('foo\bar');
$this->assertSame(array('foo', 'bar'), $name->parts);
$name = new Name($name);
$this->assertSame(array('foo', 'bar'), $name->parts);
}
public function testGet() {
@ -27,43 +30,22 @@ class NameTest extends \PHPUnit_Framework_TestCase
$this->assertSame('foo\bar', (string) $name);
$this->assertSame('foo\bar', $name->toString());
$this->assertSame('foo_bar', $name->toString('_'));
}
public function testAppend() {
$name = new Name('foo');
$name->append('bar');
$this->assertSame('foo\bar', $name->toString());
$name->append('bar\foo');
$this->assertSame('foo\bar\bar\foo', $name->toString());
}
public function testPrepend() {
$name = new Name('foo');
$name->prepend('bar');
$this->assertSame('bar\foo', $name->toString());
$name->prepend('foo\bar');
$this->assertSame('foo\bar\bar\foo', $name->toString());
}
public function testSlice() {
$name = new Name('foo\bar\baz');
$this->assertEquals(new Name('foo\bar\baz'), $name->slice(0));
$this->assertEquals(new Name('bar\baz'), $name->slice(1));
$this->assertEquals(new Name([]), $name->slice(3));
$this->assertNull($name->slice(3));
$this->assertEquals(new Name('foo\bar\baz'), $name->slice(-3));
$this->assertEquals(new Name('bar\baz'), $name->slice(-2));
$this->assertEquals(new Name('foo\bar'), $name->slice(0, -1));
$this->assertEquals(new Name([]), $name->slice(0, -3));
$this->assertNull($name->slice(0, -3));
$this->assertEquals(new Name('bar'), $name->slice(1, -1));
$this->assertEquals(new Name([]), $name->slice(1, -2));
$this->assertNull($name->slice(1, -2));
$this->assertEquals(new Name('bar'), $name->slice(-2, 1));
$this->assertEquals(new Name('bar'), $name->slice(-2, -1));
$this->assertEquals(new Name([]), $name->slice(-2, -2));
$this->assertNull($name->slice(-2, -2));
}
/**
@ -111,8 +93,9 @@ class NameTest extends \PHPUnit_Framework_TestCase
Name\Relative::concat(new Name\FullyQualified('foo\bar'), 'baz', $attributes)
);
$this->assertEquals(new Name('foo'), Name::concat([], 'foo'));
$this->assertEquals(new Name([]), Name::concat([], []));
$this->assertEquals(new Name('foo'), Name::concat(null, 'foo'));
$this->assertEquals(new Name('foo'), Name::concat('foo', null));
$this->assertNull(Name::concat(null, null));
}
public function testIs() {
@ -143,10 +126,9 @@ class NameTest extends \PHPUnit_Framework_TestCase
/**
* @expectedException \InvalidArgumentException
* @expectedExceptionMessage When changing a name you need to pass either a string, an array or a Name node
* @expectedExceptionMessage Expected string, array of parts or Name instance
*/
public function testInvalidArg() {
$name = new Name('foo');
$name->append(new \stdClass);
Name::concat('foo', new \stdClass);
}
}

View File

@ -70,6 +70,28 @@ class NodeAbstractTest extends \PHPUnit_Framework_TestCase
$this->assertNull($node->getDocComment());
}
public function testSetDocComment() {
$node = new DummyNode(null, null, []);
// Add doc comment to node without comments
$docComment = new Comment\Doc('/** doc */');
$node->setDocComment($docComment);
$this->assertSame($docComment, $node->getDocComment());
// Replace it
$docComment = new Comment\Doc('/** doc 2 */');
$node->setDocComment($docComment);
$this->assertSame($docComment, $node->getDocComment());
// Add docmment to node with other comments
$c1 = new Comment('/* foo */');
$c2 = new Comment('/* bar */');
$docComment = new Comment\Doc('/** baz */');
$node->setAttribute('comments', [$c1, $c2]);
$node->setDocComment($docComment);
$this->assertSame([$c1, $c2, $docComment], $node->getAttribute('comments'));
}
/**
* @dataProvider provideNodes
*/

View File

@ -10,7 +10,6 @@ class NodeDumperTest extends \PHPUnit_Framework_TestCase
/**
* @dataProvider provideTestDump
* @covers PhpParser\NodeDumper::dump
*/
public function testDump($node, $dump) {
$dumper = new NodeDumper;
@ -61,6 +60,40 @@ class NodeDumperTest extends \PHPUnit_Framework_TestCase
);
}
public function testDumpWithPositions() {
$parser = (new ParserFactory)->create(
ParserFactory::ONLY_PHP7,
new Lexer(['usedAttributes' => ['startLine', 'endLine', 'startFilePos', 'endFilePos']])
);
$dumper = new NodeDumper(['dumpPositions' => true]);
$code = "<?php\n\$a = 1;\necho \$a;";
$expected = <<<'OUT'
array(
0: Expr_Assign[2:1 - 2:6](
var: Expr_Variable[2:1 - 2:2](
name: a
)
expr: Scalar_LNumber[2:6 - 2:6](
value: 1
)
)
1: Stmt_Echo[3:1 - 3:8](
exprs: array(
0: Expr_Variable[3:6 - 3:7](
name: a
)
)
)
)
OUT;
$stmts = $parser->parse($code);
$dump = $dumper->dump($stmts, $code);
$this->assertSame($this->canonicalize($expected), $this->canonicalize($dump));
}
/**
* @expectedException \InvalidArgumentException
* @expectedExceptionMessage Can only dump nodes and arrays.

View File

@ -2,8 +2,8 @@
namespace PhpParser;
use PhpParser\Node\Scalar\String_;
use PhpParser\Node\Expr;
use PhpParser\Node\Scalar\String_;
class NodeTraverserTest extends \PHPUnit_Framework_TestCase
{
@ -13,7 +13,7 @@ class NodeTraverserTest extends \PHPUnit_Framework_TestCase
$echoNode = new Node\Stmt\Echo_(array($str1Node, $str2Node));
$stmts = array($echoNode);
$visitor = $this->getMock('PhpParser\NodeVisitor');
$visitor = $this->getMockBuilder('PhpParser\NodeVisitor')->getMock();
$visitor->expects($this->at(0))->method('beforeTraverse')->with($stmts);
$visitor->expects($this->at(1))->method('enterNode')->with($echoNode);
@ -36,8 +36,8 @@ class NodeTraverserTest extends \PHPUnit_Framework_TestCase
$printNode = new Expr\Print_($str1Node);
// first visitor changes the node, second verifies the change
$visitor1 = $this->getMock('PhpParser\NodeVisitor');
$visitor2 = $this->getMock('PhpParser\NodeVisitor');
$visitor1 = $this->getMockBuilder('PhpParser\NodeVisitor')->getMock();
$visitor2 = $this->getMockBuilder('PhpParser\NodeVisitor')->getMock();
// replace empty statements with string1 node
$visitor1->expects($this->at(0))->method('beforeTraverse')->with(array())
@ -81,7 +81,7 @@ class NodeTraverserTest extends \PHPUnit_Framework_TestCase
$str1Node = new String_('Foo');
$str2Node = new String_('Bar');
$visitor = $this->getMock('PhpParser\NodeVisitor');
$visitor = $this->getMockBuilder('PhpParser\NodeVisitor')->getMock();
// remove the string1 node, leave the string2 node
$visitor->expects($this->at(2))->method('leaveNode')->with($str1Node)
@ -100,7 +100,7 @@ class NodeTraverserTest extends \PHPUnit_Framework_TestCase
$strR1 = new String_('Replacement 1');
$strR2 = new String_('Replacement 2');
$visitor = $this->getMock('PhpParser\NodeVisitor');
$visitor = $this->getMockBuilder('PhpParser\NodeVisitor')->getMock();
// replace strMiddle with strR1 and strR2 by merge
$visitor->expects($this->at(4))->method('leaveNode')->with($strMiddle)
@ -119,7 +119,7 @@ class NodeTraverserTest extends \PHPUnit_Framework_TestCase
$strNode = new String_('Foo');
$stmts = array(array(array($strNode)));
$visitor = $this->getMock('PhpParser\NodeVisitor');
$visitor = $this->getMockBuilder('PhpParser\NodeVisitor')->getMock();
$visitor->expects($this->at(1))->method('enterNode')->with($strNode);
$traverser = new NodeTraverser;
@ -136,8 +136,8 @@ class NodeTraverserTest extends \PHPUnit_Framework_TestCase
$negNode = new Expr\UnaryMinus($mulNode);
$stmts = array($printNode, $negNode);
$visitor1 = $this->getMock('PhpParser\NodeVisitor');
$visitor2 = $this->getMock('PhpParser\NodeVisitor');
$visitor1 = $this->getMockBuilder('PhpParser\NodeVisitor')->getMock();
$visitor2 = $this->getMockBuilder('PhpParser\NodeVisitor')->getMock();
$visitor1->expects($this->at(1))->method('enterNode')->with($printNode)
->will($this->returnValue(NodeTraverser::DONT_TRAVERSE_CHILDREN));
@ -166,10 +166,67 @@ class NodeTraverserTest extends \PHPUnit_Framework_TestCase
$this->assertEquals($stmts, $traverser->traverse($stmts));
}
public function testStopTraversal() {
$varNode1 = new Expr\Variable('a');
$varNode2 = new Expr\Variable('b');
$varNode3 = new Expr\Variable('c');
$mulNode = new Expr\BinaryOp\Mul($varNode1, $varNode2);
$printNode = new Expr\Print_($varNode3);
$stmts = [$mulNode, $printNode];
// From enterNode() with array parent
$visitor = $this->getMockBuilder('PhpParser\NodeVisitor')->getMock();
$visitor->expects($this->at(1))->method('enterNode')->with($mulNode)
->will($this->returnValue(NodeTraverser::STOP_TRAVERSAL));
$visitor->expects($this->at(2))->method('afterTraversal');
$traverser = new NodeTraverser;
$traverser->addVisitor($visitor);
$this->assertEquals($stmts, $traverser->traverse($stmts));
// From enterNode with Node parent
$visitor = $this->getMockBuilder('PhpParser\NodeVisitor')->getMock();
$visitor->expects($this->at(2))->method('enterNode')->with($varNode1)
->will($this->returnValue(NodeTraverser::STOP_TRAVERSAL));
$visitor->expects($this->at(3))->method('afterTraversal');
$traverser = new NodeTraverser;
$traverser->addVisitor($visitor);
$this->assertEquals($stmts, $traverser->traverse($stmts));
// From leaveNode with Node parent
$visitor = $this->getMockBuilder('PhpParser\NodeVisitor')->getMock();
$visitor->expects($this->at(3))->method('leaveNode')->with($varNode1)
->will($this->returnValue(NodeTraverser::STOP_TRAVERSAL));
$visitor->expects($this->at(4))->method('afterTraversal');
$traverser = new NodeTraverser;
$traverser->addVisitor($visitor);
$this->assertEquals($stmts, $traverser->traverse($stmts));
// From leaveNode with array parent
$visitor = $this->getMockBuilder('PhpParser\NodeVisitor')->getMock();
$visitor->expects($this->at(6))->method('leaveNode')->with($mulNode)
->will($this->returnValue(NodeTraverser::STOP_TRAVERSAL));
$visitor->expects($this->at(7))->method('afterTraversal');
$traverser = new NodeTraverser;
$traverser->addVisitor($visitor);
$this->assertEquals($stmts, $traverser->traverse($stmts));
// Check that pending array modifications are still carried out
$visitor = $this->getMockBuilder('PhpParser\NodeVisitor')->getMock();
$visitor->expects($this->at(6))->method('leaveNode')->with($mulNode)
->will($this->returnValue(NodeTraverser::REMOVE_NODE));
$visitor->expects($this->at(7))->method('enterNode')->with($printNode)
->will($this->returnValue(NodeTraverser::STOP_TRAVERSAL));
$visitor->expects($this->at(8))->method('afterTraversal');
$traverser = new NodeTraverser;
$traverser->addVisitor($visitor);
$this->assertEquals([$printNode], $traverser->traverse($stmts));
}
public function testRemovingVisitor() {
$visitor1 = $this->getMock('PhpParser\NodeVisitor');
$visitor2 = $this->getMock('PhpParser\NodeVisitor');
$visitor3 = $this->getMock('PhpParser\NodeVisitor');
$visitor1 = $this->getMockBuilder('PhpParser\NodeVisitor')->getMock();
$visitor2 = $this->getMockBuilder('PhpParser\NodeVisitor')->getMock();
$visitor3 = $this->getMockBuilder('PhpParser\NodeVisitor')->getMock();
$traverser = new NodeTraverser;
$traverser->addVisitor($visitor1);
@ -185,15 +242,7 @@ class NodeTraverserTest extends \PHPUnit_Framework_TestCase
$this->assertAttributeSame($postExpected, 'visitors', $traverser, 'The appropriate visitors are not present after removal');
}
public function testCloneNodes() {
$stmts = array(new Node\Stmt\Echo_(array(new String_('Foo'), new String_('Bar'))));
$traverser = new NodeTraverser(true);
$this->assertNotSame($stmts, $traverser->traverse($stmts));
}
public function testNoCloneNodesByDefault() {
public function testNoCloneNodes() {
$stmts = array(new Node\Stmt\Echo_(array(new String_('Foo'), new String_('Bar'))));
$traverser = new NodeTraverser;
@ -208,7 +257,7 @@ class NodeTraverserTest extends \PHPUnit_Framework_TestCase
public function testReplaceByArrayOnlyAllowedIfParentIsArray() {
$stmts = array(new Node\Expr\UnaryMinus(new Node\Scalar\LNumber(42)));
$visitor = $this->getMock('PhpParser\NodeVisitor');
$visitor = $this->getMockBuilder('PhpParser\NodeVisitor')->getMock();
$visitor->method('leaveNode')->willReturn(array(new Node\Scalar\DNumber(42.0)));
$traverser = new NodeTraverser();

View File

@ -4,9 +4,9 @@ namespace PhpParser\NodeVisitor;
use PhpParser;
use PhpParser\Node;
use PhpParser\Node\Expr;
use PhpParser\Node\Name;
use PhpParser\Node\Stmt;
use PhpParser\Node\Expr;
class NameResolverTest extends \PHPUnit_Framework_TestCase
{
@ -118,8 +118,8 @@ namespace {
new \Hallo\Bar();
new \Bar();
new \Bar();
bar();
hi();
\bar();
\hi();
\Hallo\bar();
\foo\bar();
\bar();
@ -199,9 +199,12 @@ interface A extends C, D {
public function a(A $a) : A;
}
function fn() : A {}
function fn2() : array {}
function() : A {};
function fn(A $a) : A {}
function fn2(array $a) : array {}
function(A $a) : A {};
function fn3(?A $a) : ?A {}
function fn4(?array $a) : ?array {}
A::b();
A::$b;
@ -233,14 +236,20 @@ interface A extends \NS\C, \NS\D
{
public function a(\NS\A $a) : \NS\A;
}
function fn() : \NS\A
function fn(\NS\A $a) : \NS\A
{
}
function fn2() : array
function fn2(array $a) : array
{
}
function () : \NS\A {
function (\NS\A $a) : \NS\A {
};
function fn3(?\NS\A $a) : ?\NS\A
{
}
function fn4(?array $a) : ?array
{
}
\NS\A::b();
\NS\A::$b;
\NS\A::B;
@ -278,7 +287,7 @@ EOC;
$this->assertEquals($stmts, $traverser->traverse($stmts));
}
public function testAddNamespacedName() {
public function testAddDeclarationNamespacedName() {
$nsStmts = array(
new Stmt\Class_('A'),
new Stmt\Interface_('B'),
@ -310,6 +319,29 @@ EOC;
$this->assertObjectNotHasAttribute('namespacedName', $stmts[0]->stmts[5]->class);
}
public function testAddRuntimeResolvedNamespacedName() {
$stmts = array(
new Stmt\Namespace_(new Name('NS'), array(
new Expr\FuncCall(new Name('foo')),
new Expr\ConstFetch(new Name('FOO')),
)),
new Stmt\Namespace_(null, array(
new Expr\FuncCall(new Name('foo')),
new Expr\ConstFetch(new Name('FOO')),
)),
);
$traverser = new PhpParser\NodeTraverser;
$traverser->addVisitor(new NameResolver);
$stmts = $traverser->traverse($stmts);
$this->assertSame('NS\\foo', (string) $stmts[0]->stmts[0]->name->getAttribute('namespacedName'));
$this->assertSame('NS\\FOO', (string) $stmts[0]->stmts[1]->name->getAttribute('namespacedName'));
$this->assertFalse($stmts[1]->stmts[0]->name->hasAttribute('namespacedName'));
$this->assertFalse($stmts[1]->stmts[1]->name->hasAttribute('namespacedName'));
}
/**
* @dataProvider provideTestError
*/
@ -414,4 +446,23 @@ EOC;
$this->assertSame('PARENT', (string)$methodStmt->stmts[1]->class);
$this->assertSame('STATIC', (string)$methodStmt->stmts[2]->class);
}
public function testAddOriginalNames() {
$traverser = new PhpParser\NodeTraverser;
$traverser->addVisitor(new NameResolver(null, ['preserveOriginalNames' => true]));
$n1 = new Name('Bar');
$n2 = new Name('bar');
$origStmts = [
new Stmt\Namespace_(new Name('Foo'), [
new Expr\ClassConstFetch($n1, 'FOO'),
new Expr\FuncCall($n2),
])
];
$stmts = $traverser->traverse($origStmts);
$this->assertSame($n1, $stmts[0]->stmts[0]->class->getAttribute('originalName'));
$this->assertSame($n2, $stmts[0]->stmts[1]->name->getAttribute('originalName'));
}
}

View File

@ -4,10 +4,10 @@ namespace PhpParser\Parser;
use PhpParser\Error;
use PhpParser\Lexer;
use PhpParser\Node\Scalar\LNumber;
use PhpParser\ParserTest;
use PhpParser\Node\Expr;
use PhpParser\Node\Scalar\LNumber;
use PhpParser\Node\Stmt;
use PhpParser\ParserTest;
require_once __DIR__ . '/../ParserTest.php';
@ -30,7 +30,6 @@ class MultipleTest extends ParserTest {
/** @dataProvider provideTestParse */
public function testParse($code, Multiple $parser, $expected) {
$this->assertEquals($expected, $parser->parse($code));
$this->assertSame([], $parser->getErrors());
}
public function provideTestParse() {
@ -92,22 +91,4 @@ class MultipleTest extends ParserTest {
$parser = new Multiple([$parserA, $parserB]);
$parser->parse('dummy');
}
public function testGetErrors() {
$errorsA = [new Error('A1'), new Error('A2')];
$parserA = $this->getMockBuilder('PhpParser\Parser')->getMock();
$parserA->expects($this->at(0))->method('parse');
$parserA->expects($this->at(1))
->method('getErrors')->will($this->returnValue($errorsA));
$errorsB = [new Error('B1'), new Error('B2')];
$parserB = $this->getMockBuilder('PhpParser\Parser')->getMock();
$parserB->expects($this->at(0))->method('parse');
$parserB->expects($this->at(1))
->method('getErrors')->will($this->returnValue($errorsB));
$parser = new Multiple([$parserA, $parserB]);
$parser->parse('dummy');
$this->assertSame($errorsA, $parser->getErrors());
}
}

View File

@ -30,6 +30,15 @@ abstract class ParserTest extends \PHPUnit_Framework_TestCase
$parser->parse('<?php use foo as self;');
}
/**
* @expectedException \PhpParser\Error
* @expectedExceptionMessage Unterminated comment on line 1
*/
public function testParserThrowsLexerError() {
$parser = $this->getParser(new Lexer());
$parser->parse('<?php /*');
}
public function testAttributeAssignment() {
$lexer = new Lexer(array(
'usedAttributes' => array(

View File

@ -4,8 +4,11 @@ namespace PhpParser;
use PhpParser\Comment;
use PhpParser\Node\Expr;
use PhpParser\Node\Name;
use PhpParser\Node\Scalar\DNumber;
use PhpParser\Node\Scalar\Encapsed;
use PhpParser\Node\Scalar\EncapsedStringPart;
use PhpParser\Node\Scalar\LNumber;
use PhpParser\Node\Scalar\String_;
use PhpParser\Node\Stmt;
use PhpParser\PrettyPrinter\Standard;
@ -161,4 +164,44 @@ class PrettyPrinterTest extends CodeTestAbstract
[new Encapsed([new EncapsedStringPart("STR")], $heredoc), '"STR"'],
];
}
/** @dataProvider provideTestUnnaturalLiterals */
public function testUnnaturalLiterals($node, $expected) {
$prttyPrinter = new PrettyPrinter\Standard;
$result = $prttyPrinter->prettyPrintExpr($node);
$this->assertSame($expected, $result);
}
public function provideTestUnnaturalLiterals() {
return [
[new LNumber(-1), '-1'],
[new LNumber(-PHP_INT_MAX - 1), '(-' . PHP_INT_MAX . '-1)'],
[new LNumber(-1, ['kind' => LNumber::KIND_BIN]), '-0b1'],
[new LNumber(-1, ['kind' => LNumber::KIND_OCT]), '-01'],
[new LNumber(-1, ['kind' => LNumber::KIND_HEX]), '-0x1'],
[new DNumber(\INF), '\INF'],
[new DNumber(-\INF), '-\INF'],
[new DNumber(-\NAN), '\NAN'],
];
}
/**
* @expectedException \LogicException
* @expectedExceptionMessage Cannot pretty-print AST with Error nodes
*/
public function testPrettyPrintWithError() {
$stmts = [new Expr\PropertyFetch(new Expr\Variable('a'), new Expr\Error())];
$prettyPrinter = new PrettyPrinter\Standard;
$prettyPrinter->prettyPrint($stmts);
}
/**
* @expectedException \LogicException
* @expectedExceptionMessage Cannot pretty-print AST with Error nodes
*/
public function testPrettyPrintWithErrorInClassConstFetch() {
$stmts = [new Expr\ClassConstFetch(new Name('Foo'), new Expr\Error())];
$prettyPrinter = new PrettyPrinter\Standard;
$prettyPrinter->prettyPrint($stmts);
}
}

View File

@ -2,8 +2,8 @@
namespace PhpParser\Unserializer;
use PhpParser\Node\Scalar;
use PhpParser\Comment;
use PhpParser\Node\Scalar;
class XMLTest extends \PHPUnit_Framework_TestCase
{

View File

@ -0,0 +1,124 @@
Lexer errors
-----
<?php
$a = 42;
/*
$b = 24;
-----
Unterminated comment from 4:1 to 5:9
array(
0: Expr_Assign(
var: Expr_Variable(
name: a
)
expr: Scalar_LNumber(
value: 42
)
)
1: Stmt_Nop(
comments: array(
0: /*
$b = 24;
)
)
)
-----
<?php
$a = 42;
@@{ "\1" }@@
$b = 24;
-----
Unexpected character "@@{ "\1" }@@" (ASCII 1) from 4:1 to 4:1
array(
0: Expr_Assign(
var: Expr_Variable(
name: a
)
expr: Scalar_LNumber(
value: 42
)
)
1: Expr_Assign(
var: Expr_Variable(
name: b
)
expr: Scalar_LNumber(
value: 24
)
)
)
-----
<?php
$a = 42;
@@{ "\0" }@@
$b = 24;
-----
Unexpected null byte from 4:1 to 4:1
array(
0: Expr_Assign(
var: Expr_Variable(
name: a
)
expr: Scalar_LNumber(
value: 42
)
)
1: Expr_Assign(
var: Expr_Variable(
name: b
)
expr: Scalar_LNumber(
value: 24
)
)
)
-----
<?php
$a = 1;
@@{ "\1" }@@
$b = 2;
@@{ "\2" }@@
$c = 3;
-----
Unexpected character "@@{ "\1" }@@" (ASCII 1) from 4:1 to 4:1
Unexpected character "@@{ "\2" }@@" (ASCII 2) from 6:1 to 6:1
array(
0: Expr_Assign(
var: Expr_Variable(
name: a
)
expr: Scalar_LNumber(
value: 1
)
)
1: Expr_Assign(
var: Expr_Variable(
name: b
)
expr: Scalar_LNumber(
value: 2
)
)
2: Expr_Assign(
var: Expr_Variable(
name: c
)
expr: Scalar_LNumber(
value: 3
)
)
)
-----
<?php
if ($b) {
$a = 1;
/* unterminated
}
-----
Unterminated comment from 5:5 to 6:2
Syntax error, unexpected EOF from 6:2 to 6:2

View File

@ -245,13 +245,14 @@ Syntax error, unexpected EOF from 8:12 to 8:12
$foo->
;
-----
Syntax error, unexpected ';' from 3:1 to 3:1
!!positions
Syntax error, unexpected ';', expecting T_STRING or T_VARIABLE or '{' or '$' from 3:1 to 3:1
array(
0: Expr_PropertyFetch(
var: Expr_Variable(
0: Expr_PropertyFetch[2:1 - 3:1](
var: Expr_Variable[2:1 - 2:4](
name: foo
)
name: Expr_Error(
name: Expr_Error[3:1 - 3:1](
)
)
)
@ -261,22 +262,114 @@ function foo() {
$bar->
}
-----
Syntax error, unexpected '}' from 4:1 to 4:1
!!positions
Syntax error, unexpected '}', expecting T_STRING or T_VARIABLE or '{' or '$' from 4:1 to 4:1
array(
0: Stmt_Function(
0: Stmt_Function[2:1 - 4:1](
byRef: false
name: foo
params: array(
)
returnType: null
stmts: array(
0: Expr_PropertyFetch(
var: Expr_Variable(
0: Expr_PropertyFetch[3:5 - 4:1](
var: Expr_Variable[3:5 - 3:8](
name: bar
)
name: Expr_Error(
name: Expr_Error[4:1 - 4:1](
)
)
)
)
)
-----
<?php
new T
-----
Syntax error, unexpected EOF from 2:6 to 2:6
array(
0: Expr_New(
class: Name(
parts: array(
0: T
)
)
args: array(
)
)
)
-----
<?php
new
-----
!!php7,positions
Syntax error, unexpected EOF from 2:4 to 2:4
array(
0: Expr_New[2:1 - 2:4](
class: Expr_Error[2:4 - 2:4](
)
args: array(
)
)
)
-----
<?php
$foo instanceof
-----
!!php7
Syntax error, unexpected EOF from 2:16 to 2:16
array(
0: Expr_Instanceof(
expr: Expr_Variable(
name: foo
)
class: Expr_Error(
)
)
)
-----
<?php
$
-----
!!php7
Syntax error, unexpected EOF, expecting T_VARIABLE or '{' or '$' from 2:2 to 2:2
array(
0: Expr_Variable(
name: Expr_Error(
)
)
)
-----
<?php
Foo::$
-----
!!php7
Syntax error, unexpected EOF, expecting T_VARIABLE or '{' or '$' from 2:7 to 2:7
array(
0: Expr_StaticPropertyFetch(
class: Name(
parts: array(
0: Foo
)
)
name: Expr_Error(
)
)
)
-----
<?php
Foo::
-----
!!php7
Syntax error, unexpected EOF from 2:6 to 2:6
array(
0: Expr_ClassConstFetch(
class: Name(
parts: array(
0: Foo
)
)
name: Expr_Error(
)
)
)

View File

@ -246,17 +246,21 @@ array(
)
byRef: false
)
1: Expr_List(
items: array(
0: null
1: Expr_ArrayItem(
key: null
value: Expr_Variable(
name: c
1: Expr_ArrayItem(
key: null
value: Expr_List(
items: array(
0: null
1: Expr_ArrayItem(
key: null
value: Expr_Variable(
name: c
)
byRef: false
)
byRef: false
)
)
byRef: false
)
2: Expr_ArrayItem(
key: null

View File

@ -3,6 +3,21 @@ New without a class
<?php
new;
-----
!!php5
Syntax error, unexpected ';' from 2:4 to 2:4
array(
)
-----
<?php
new;
-----
!!php7
Syntax error, unexpected ';' from 2:4 to 2:4
array(
0: Expr_New(
class: Expr_Error(
)
args: array(
)
)
)

View File

@ -0,0 +1,72 @@
Encapsed string negative var offsets
-----
<?php
"$a[-0]";
"$a[-1]";
"$a[-0x0]";
"$a[-00]";
"$a[@@{ -PHP_INT_MAX - 1 }@@]";
-----
!!php7
array(
0: Scalar_Encapsed(
parts: array(
0: Expr_ArrayDimFetch(
var: Expr_Variable(
name: a
)
dim: Scalar_String(
value: -0
)
)
)
)
1: Scalar_Encapsed(
parts: array(
0: Expr_ArrayDimFetch(
var: Expr_Variable(
name: a
)
dim: Scalar_LNumber(
value: -1
)
)
)
)
2: Scalar_Encapsed(
parts: array(
0: Expr_ArrayDimFetch(
var: Expr_Variable(
name: a
)
dim: Scalar_String(
value: -0x0
)
)
)
)
3: Scalar_Encapsed(
parts: array(
0: Expr_ArrayDimFetch(
var: Expr_Variable(
name: a
)
dim: Scalar_String(
value: -00
)
)
)
)
4: Scalar_Encapsed(
parts: array(
0: Expr_ArrayDimFetch(
var: Expr_Variable(
name: a
)
dim: Scalar_LNumber(
value: @@{ -PHP_INT_MAX - 1 }@@
)
)
)
)
)

View File

@ -6,7 +6,11 @@ Encapsed strings
"$A->B";
"$A[B]";
"$A[0]";
"$A[1234]";
"$A[9223372036854775808]";
"$A[000]";
"$A[0x0]";
"$A[0b0]";
"$A[$B]";
"{$A}";
"{$A['B']}";
@ -59,13 +63,49 @@ array(
var: Expr_Variable(
name: A
)
dim: Scalar_String(
dim: Scalar_LNumber(
value: 0
)
)
)
)
4: Scalar_Encapsed(
parts: array(
0: Expr_ArrayDimFetch(
var: Expr_Variable(
name: A
)
dim: Scalar_LNumber(
value: 1234
)
)
)
)
5: Scalar_Encapsed(
parts: array(
0: Expr_ArrayDimFetch(
var: Expr_Variable(
name: A
)
dim: Scalar_String(
value: 9223372036854775808
)
)
)
)
6: Scalar_Encapsed(
parts: array(
0: Expr_ArrayDimFetch(
var: Expr_Variable(
name: A
)
dim: Scalar_String(
value: 000
)
)
)
)
7: Scalar_Encapsed(
parts: array(
0: Expr_ArrayDimFetch(
var: Expr_Variable(
@ -77,7 +117,19 @@ array(
)
)
)
5: Scalar_Encapsed(
8: Scalar_Encapsed(
parts: array(
0: Expr_ArrayDimFetch(
var: Expr_Variable(
name: A
)
dim: Scalar_String(
value: 0b0
)
)
)
)
9: Scalar_Encapsed(
parts: array(
0: Expr_ArrayDimFetch(
var: Expr_Variable(
@ -89,45 +141,45 @@ array(
)
)
)
6: Scalar_Encapsed(
parts: array(
0: Expr_Variable(
name: A
)
)
)
7: Scalar_Encapsed(
parts: array(
0: Expr_ArrayDimFetch(
var: Expr_Variable(
name: A
)
dim: Scalar_String(
value: B
)
)
)
)
8: Scalar_Encapsed(
parts: array(
0: Expr_Variable(
name: A
)
)
)
9: Scalar_Encapsed(
parts: array(
0: Expr_ArrayDimFetch(
var: Expr_Variable(
name: A
)
dim: Scalar_String(
value: B
)
)
)
)
10: Scalar_Encapsed(
parts: array(
0: Expr_Variable(
name: A
)
)
)
11: Scalar_Encapsed(
parts: array(
0: Expr_ArrayDimFetch(
var: Expr_Variable(
name: A
)
dim: Scalar_String(
value: B
)
)
)
)
12: Scalar_Encapsed(
parts: array(
0: Expr_Variable(
name: A
)
)
)
13: Scalar_Encapsed(
parts: array(
0: Expr_ArrayDimFetch(
var: Expr_Variable(
name: A
)
dim: Scalar_String(
value: B
)
)
)
)
14: Scalar_Encapsed(
parts: array(
0: Expr_Variable(
name: Expr_Variable(
@ -136,7 +188,7 @@ array(
)
)
)
11: Scalar_Encapsed(
15: Scalar_Encapsed(
parts: array(
0: Scalar_EncapsedStringPart(
value: \{
@ -149,7 +201,7 @@ array(
)
)
)
12: Scalar_Encapsed(
16: Scalar_Encapsed(
parts: array(
0: Scalar_EncapsedStringPart(
value: \{
@ -162,7 +214,7 @@ array(
)
)
)
13: Scalar_Encapsed(
17: Scalar_Encapsed(
parts: array(
0: Scalar_EncapsedStringPart(
value: \
@ -172,7 +224,7 @@ array(
)
)
)
14: Scalar_Encapsed(
18: Scalar_Encapsed(
parts: array(
0: Scalar_EncapsedStringPart(
value: \{
@ -185,7 +237,7 @@ array(
)
)
)
15: Scalar_Encapsed(
19: Scalar_Encapsed(
parts: array(
0: Expr_Variable(
name: Expr_Variable(
@ -197,7 +249,7 @@ array(
)
)
)
16: Scalar_Encapsed(
20: Scalar_Encapsed(
parts: array(
0: Scalar_EncapsedStringPart(
value: $
@ -212,7 +264,7 @@ array(
)
)
)
17: Scalar_Encapsed(
21: Scalar_Encapsed(
parts: array(
0: Scalar_EncapsedStringPart(
value: A
@ -225,14 +277,14 @@ array(
)
)
)
18: Scalar_Encapsed(
22: Scalar_Encapsed(
parts: array(
0: Expr_Variable(
name: A
)
)
)
19: Scalar_Encapsed(
23: Scalar_Encapsed(
parts: array(
0: Expr_Variable(
name: A

View File

@ -5,6 +5,11 @@ Invalid octal literals
-----
!!php7
Invalid numeric literal from 2:1 to 2:4
array(
0: Scalar_LNumber(
value: 0
)
)
-----
<?php
0787;

View File

@ -6,7 +6,29 @@ class A {
}
-----
!!php7
Cannot use 'static' as constant modifier on line 3
Cannot use 'static' as constant modifier from 3:5 to 3:10
array(
0: Stmt_Class(
flags: 0
name: A
extends: null
implements: array(
)
stmts: array(
0: Stmt_ClassConst(
flags: MODIFIER_STATIC (8)
consts: array(
0: Const(
name: X
value: Scalar_LNumber(
value: 1
)
)
)
)
)
)
)
-----
<?php
class A {
@ -14,7 +36,29 @@ class A {
}
-----
!!php7
Cannot use 'abstract' as constant modifier on line 3
Cannot use 'abstract' as constant modifier from 3:5 to 3:12
array(
0: Stmt_Class(
flags: 0
name: A
extends: null
implements: array(
)
stmts: array(
0: Stmt_ClassConst(
flags: MODIFIER_ABSTRACT (16)
consts: array(
0: Const(
name: X
value: Scalar_LNumber(
value: 1
)
)
)
)
)
)
)
-----
<?php
class A {
@ -22,11 +66,56 @@ class A {
}
-----
!!php7
Cannot use 'final' as constant modifier on line 3
Cannot use 'final' as constant modifier from 3:5 to 3:9
array(
0: Stmt_Class(
flags: 0
name: A
extends: null
implements: array(
)
stmts: array(
0: Stmt_ClassConst(
flags: MODIFIER_FINAL (32)
consts: array(
0: Const(
name: X
value: Scalar_LNumber(
value: 1
)
)
)
)
)
)
)
-----
<?php
class A {
public public const X = 1;
}
-----
Multiple access type modifiers are not allowed on line 3
!!php7
Multiple access type modifiers are not allowed from 3:12 to 3:17
array(
0: Stmt_Class(
flags: 0
name: A
extends: null
implements: array(
)
stmts: array(
0: Stmt_ClassConst(
flags: MODIFIER_PUBLIC (1)
consts: array(
0: Const(
name: X
value: Scalar_LNumber(
value: 1
)
)
)
)
)
)
)

View File

@ -2,27 +2,148 @@ Invalid modifier combination
-----
<?php class A { public public $a; }
-----
Multiple access type modifiers are not allowed on line 1
Multiple access type modifiers are not allowed from 1:24 to 1:29
array(
0: Stmt_Class(
flags: 0
name: A
extends: null
implements: array(
)
stmts: array(
0: Stmt_Property(
flags: MODIFIER_PUBLIC (1)
props: array(
0: Stmt_PropertyProperty(
name: a
default: null
)
)
)
)
)
)
-----
<?php class A { public protected $a; }
-----
Multiple access type modifiers are not allowed on line 1
Multiple access type modifiers are not allowed from 1:24 to 1:32
array(
0: Stmt_Class(
flags: 0
name: A
extends: null
implements: array(
)
stmts: array(
0: Stmt_Property(
flags: MODIFIER_PUBLIC | MODIFIER_PROTECTED (3)
props: array(
0: Stmt_PropertyProperty(
name: a
default: null
)
)
)
)
)
)
-----
<?php class A { abstract abstract a(); }
<?php class A { abstract abstract function a(); }
-----
Multiple abstract modifiers are not allowed on line 1
Multiple abstract modifiers are not allowed from 1:26 to 1:33
array(
0: Stmt_Class(
flags: 0
name: A
extends: null
implements: array(
)
stmts: array(
0: Stmt_ClassMethod(
flags: MODIFIER_ABSTRACT (16)
byRef: false
name: a
params: array(
)
returnType: null
stmts: null
)
)
)
)
-----
<?php class A { static static $a; }
-----
Multiple static modifiers are not allowed on line 1
Multiple static modifiers are not allowed from 1:24 to 1:29
array(
0: Stmt_Class(
flags: 0
name: A
extends: null
implements: array(
)
stmts: array(
0: Stmt_Property(
flags: MODIFIER_STATIC (8)
props: array(
0: Stmt_PropertyProperty(
name: a
default: null
)
)
)
)
)
)
-----
<?php class A { final final a() {} }
<?php class A { final final function a() {} }
-----
Multiple final modifiers are not allowed on line 1
Multiple final modifiers are not allowed from 1:23 to 1:27
array(
0: Stmt_Class(
flags: 0
name: A
extends: null
implements: array(
)
stmts: array(
0: Stmt_ClassMethod(
flags: MODIFIER_FINAL (32)
byRef: false
name: a
params: array(
)
returnType: null
stmts: array(
)
)
)
)
)
-----
<?php class A { abstract final a(); }
<?php class A { abstract final function a(); }
-----
Cannot use the final modifier on an abstract class member on line 1
Cannot use the final modifier on an abstract class member from 1:26 to 1:30
array(
0: Stmt_Class(
flags: 0
name: A
extends: null
implements: array(
)
stmts: array(
0: Stmt_ClassMethod(
flags: MODIFIER_ABSTRACT | MODIFIER_FINAL (48)
byRef: false
name: a
params: array(
)
returnType: null
stmts: null
)
)
)
)
-----
<?php abstract final class A { }
// Type in the partial parse could conceivably be any of 0, 16 or 32
@ -47,8 +168,48 @@ array(
-----
<?php class A { abstract $a; }
-----
Properties cannot be declared abstract on line 1
Properties cannot be declared abstract from 1:17 to 1:24
array(
0: Stmt_Class(
flags: 0
name: A
extends: null
implements: array(
)
stmts: array(
0: Stmt_Property(
flags: MODIFIER_ABSTRACT (16)
props: array(
0: Stmt_PropertyProperty(
name: a
default: null
)
)
)
)
)
)
-----
<?php class A { final $a; }
-----
Properties cannot be declared final on line 1
Properties cannot be declared final from 1:17 to 1:21
array(
0: Stmt_Class(
flags: 0
name: A
extends: null
implements: array(
)
stmts: array(
0: Stmt_Property(
flags: MODIFIER_FINAL (32)
props: array(
0: Stmt_PropertyProperty(
name: a
default: null
)
)
)
)
)
)

View File

@ -2,11 +2,33 @@ Invalid class name
-----
<?php class self {}
-----
Cannot use 'self' as class name as it is reserved on line 1
Cannot use 'self' as class name as it is reserved from 1:13 to 1:16
array(
0: Stmt_Class(
flags: 0
name: self
extends: null
implements: array(
)
stmts: array(
)
)
)
-----
<?php class PARENT {}
-----
Cannot use 'PARENT' as class name as it is reserved on line 1
Cannot use 'PARENT' as class name as it is reserved from 1:13 to 1:18
array(
0: Stmt_Class(
flags: 0
name: PARENT
extends: null
implements: array(
)
stmts: array(
)
)
)
-----
<?php class static {}
-----
@ -17,10 +39,40 @@ array(
<?php class A extends self {}
-----
Cannot use 'self' as class name as it is reserved from 1:23 to 1:26
array(
0: Stmt_Class(
flags: 0
name: A
extends: Name(
parts: array(
0: self
)
)
implements: array(
)
stmts: array(
)
)
)
-----
<?php class A extends PARENT {}
-----
Cannot use 'PARENT' as class name as it is reserved from 1:23 to 1:28
array(
0: Stmt_Class(
flags: 0
name: A
extends: Name(
parts: array(
0: PARENT
)
)
implements: array(
)
stmts: array(
)
)
)
-----
<?php class A extends static {}
-----
@ -31,10 +83,42 @@ array(
<?php class A implements self {}
-----
Cannot use 'self' as interface name as it is reserved from 1:26 to 1:29
array(
0: Stmt_Class(
flags: 0
name: A
extends: null
implements: array(
0: Name(
parts: array(
0: self
)
)
)
stmts: array(
)
)
)
-----
<?php class A implements PARENT {}
-----
Cannot use 'PARENT' as interface name as it is reserved from 1:26 to 1:31
array(
0: Stmt_Class(
flags: 0
name: A
extends: null
implements: array(
0: Name(
parts: array(
0: PARENT
)
)
)
stmts: array(
)
)
)
-----
<?php class A implements static {}
-----
@ -44,11 +128,29 @@ array(
-----
<?php interface self {}
-----
Cannot use 'self' as class name as it is reserved on line 1
Cannot use 'self' as class name as it is reserved from 1:17 to 1:20
array(
0: Stmt_Interface(
name: self
extends: array(
)
stmts: array(
)
)
)
-----
<?php interface PARENT {}
-----
Cannot use 'PARENT' as class name as it is reserved on line 1
Cannot use 'PARENT' as class name as it is reserved from 1:17 to 1:22
array(
0: Stmt_Interface(
name: PARENT
extends: array(
)
stmts: array(
)
)
)
-----
<?php interface static {}
-----
@ -59,10 +161,38 @@ array(
<?php interface A extends self {}
-----
Cannot use 'self' as interface name as it is reserved from 1:27 to 1:30
array(
0: Stmt_Interface(
name: A
extends: array(
0: Name(
parts: array(
0: self
)
)
)
stmts: array(
)
)
)
-----
<?php interface A extends PARENT {}
-----
Cannot use 'PARENT' as interface name as it is reserved from 1:27 to 1:32
array(
0: Stmt_Interface(
name: A
extends: array(
0: Name(
parts: array(
0: PARENT
)
)
)
stmts: array(
)
)
)
-----
<?php interface A extends static {}
-----

View File

@ -2,24 +2,150 @@ Some special methods cannot be static
-----
<?php class A { static function __construct() {} }
-----
Constructor __construct() cannot be static on line 1
Constructor __construct() cannot be static from 1:17 to 1:22
array(
0: Stmt_Class(
flags: 0
name: A
extends: null
implements: array(
)
stmts: array(
0: Stmt_ClassMethod(
flags: MODIFIER_STATIC (8)
byRef: false
name: __construct
params: array(
)
returnType: null
stmts: array(
)
)
)
)
)
-----
<?php class A { static function __destruct() {} }
-----
Destructor __destruct() cannot be static on line 1
Destructor __destruct() cannot be static from 1:17 to 1:22
array(
0: Stmt_Class(
flags: 0
name: A
extends: null
implements: array(
)
stmts: array(
0: Stmt_ClassMethod(
flags: MODIFIER_STATIC (8)
byRef: false
name: __destruct
params: array(
)
returnType: null
stmts: array(
)
)
)
)
)
-----
<?php class A { static function __clone() {} }
-----
Clone method __clone() cannot be static on line 1
Clone method __clone() cannot be static from 1:17 to 1:22
array(
0: Stmt_Class(
flags: 0
name: A
extends: null
implements: array(
)
stmts: array(
0: Stmt_ClassMethod(
flags: MODIFIER_STATIC (8)
byRef: false
name: __clone
params: array(
)
returnType: null
stmts: array(
)
)
)
)
)
-----
<?php class A { static function __CONSTRUCT() {} }
-----
Constructor __CONSTRUCT() cannot be static on line 1
Constructor __CONSTRUCT() cannot be static from 1:17 to 1:22
array(
0: Stmt_Class(
flags: 0
name: A
extends: null
implements: array(
)
stmts: array(
0: Stmt_ClassMethod(
flags: MODIFIER_STATIC (8)
byRef: false
name: __CONSTRUCT
params: array(
)
returnType: null
stmts: array(
)
)
)
)
)
-----
<?php class A { static function __Destruct() {} }
-----
Destructor __Destruct() cannot be static on line 1
Destructor __Destruct() cannot be static from 1:17 to 1:22
array(
0: Stmt_Class(
flags: 0
name: A
extends: null
implements: array(
)
stmts: array(
0: Stmt_ClassMethod(
flags: MODIFIER_STATIC (8)
byRef: false
name: __Destruct
params: array(
)
returnType: null
stmts: array(
)
)
)
)
)
-----
<?php class A { static function __cLoNe() {} }
-----
Clone method __cLoNe() cannot be static on line 1
Clone method __cLoNe() cannot be static from 1:17 to 1:22
array(
0: Stmt_Class(
flags: 0
name: A
extends: null
implements: array(
)
stmts: array(
0: Stmt_ClassMethod(
flags: MODIFIER_STATIC (8)
byRef: false
name: __cLoNe
params: array(
)
returnType: null
stmts: array(
)
)
)
)
)

View File

@ -4,3 +4,24 @@ Invalid variadic function
function foo(...$foo = []) {}
-----
Variadic parameter cannot have a default value from 2:24 to 2:25
array(
0: Stmt_Function(
byRef: false
name: foo
params: array(
0: Param(
type: null
byRef: false
variadic: true
name: foo
default: Expr_Array(
items: array(
)
)
)
)
returnType: null
stmts: array(
)
)
)

View File

@ -0,0 +1,28 @@
Ensure correct file position attributes for group use prefix
-----
<?php
use Foo\Bar\{Baz};
-----
!!positions
array(
0: Stmt_GroupUse[2:1 - 2:17](
type: TYPE_UNKNOWN (0)
prefix: Name[2:5 - 2:11](
parts: array(
0: Foo
1: Bar
)
)
uses: array(
0: Stmt_UseUse[2:14 - 2:16](
type: TYPE_NORMAL (1)
name: Name[2:14 - 2:16](
parts: array(
0: Baz
)
)
alias: Baz
)
)
)
)

View File

@ -3,10 +3,32 @@ Invalid namespace names
<?php namespace self;
-----
Cannot use 'self' as namespace name from 1:17 to 1:20
array(
0: Stmt_Namespace(
name: Name(
parts: array(
0: self
)
)
stmts: array(
)
)
)
-----
<?php namespace PARENT;
-----
Cannot use 'PARENT' as namespace name from 1:17 to 1:22
array(
0: Stmt_Namespace(
name: Name(
parts: array(
0: PARENT
)
)
stmts: array(
)
)
)
-----
<?php namespace static;
-----
@ -16,11 +38,43 @@ array(
-----
<?php use A as self;
-----
Cannot use A as self because 'self' is a special class name on line 1
Cannot use A as self because 'self' is a special class name from 1:16 to 1:19
array(
0: Stmt_Use(
type: TYPE_NORMAL (1)
uses: array(
0: Stmt_UseUse(
type: TYPE_UNKNOWN (0)
name: Name(
parts: array(
0: A
)
)
alias: self
)
)
)
)
-----
<?php use B as PARENT;
-----
Cannot use B as PARENT because 'PARENT' is a special class name on line 1
Cannot use B as PARENT because 'PARENT' is a special class name from 1:16 to 1:21
array(
0: Stmt_Use(
type: TYPE_NORMAL (1)
uses: array(
0: Stmt_UseUse(
type: TYPE_UNKNOWN (0)
name: Name(
parts: array(
0: B
)
)
alias: PARENT
)
)
)
)
-----
<?php use C as static;
-----

View File

@ -2,12 +2,102 @@ Namespace types cannot be mixed
-----
<?php
namespace A;
namespace B {}
echo 1;
namespace B {
echo 2;
}
echo 3;
-----
Cannot mix bracketed namespace declarations with unbracketed namespace declarations on line 3
Cannot mix bracketed namespace declarations with unbracketed namespace declarations on line 4
array(
0: Stmt_Namespace(
name: Name(
parts: array(
0: A
)
)
stmts: array(
0: Stmt_Echo(
exprs: array(
0: Scalar_LNumber(
value: 1
)
)
)
)
)
1: Stmt_Namespace(
name: Name(
parts: array(
0: B
)
)
stmts: array(
0: Stmt_Echo(
exprs: array(
0: Scalar_LNumber(
value: 2
)
)
)
)
)
2: Stmt_Echo(
exprs: array(
0: Scalar_LNumber(
value: 3
)
)
)
)
-----
<?php
namespace A {}
namespace A {
echo 1;
}
echo 2;
namespace B;
echo 3;
-----
Cannot mix bracketed namespace declarations with unbracketed namespace declarations on line 3
Cannot mix bracketed namespace declarations with unbracketed namespace declarations on line 6
array(
0: Stmt_Namespace(
name: Name(
parts: array(
0: A
)
)
stmts: array(
0: Stmt_Echo(
exprs: array(
0: Scalar_LNumber(
value: 1
)
)
)
)
)
1: Stmt_Echo(
exprs: array(
0: Scalar_LNumber(
value: 2
)
)
)
2: Stmt_Namespace(
name: Name(
parts: array(
0: B
)
)
stmts: array(
0: Stmt_Echo(
exprs: array(
0: Scalar_LNumber(
value: 3
)
)
)
)
)
)

View File

@ -8,3 +8,23 @@ namespace A {
}
-----
Namespace declarations cannot be nested from 3:5 to 5:5
array(
0: Stmt_Namespace(
name: Name(
parts: array(
0: A
)
)
stmts: array(
0: Stmt_Namespace(
name: Name(
parts: array(
0: B
)
)
stmts: array(
)
)
)
)
)

View File

@ -2,19 +2,104 @@ There (mostly) can't be statements outside of namespaces
-----
<?php
echo 1;
echo 2;
namespace A;
-----
Namespace declaration statement has to be the very first statement in the script on line 3
Namespace declaration statement has to be the very first statement in the script on line 4
array(
0: Stmt_Echo(
exprs: array(
0: Scalar_LNumber(
value: 1
)
)
)
1: Stmt_Echo(
exprs: array(
0: Scalar_LNumber(
value: 2
)
)
)
2: Stmt_Namespace(
name: Name(
parts: array(
0: A
)
)
stmts: array(
)
)
)
-----
<?php
namespace A {}
echo 1;
-----
No code may exist outside of namespace {} on line 3
No code may exist outside of namespace {} from 3:1 to 3:7
array(
0: Stmt_Namespace(
name: Name(
parts: array(
0: A
)
)
stmts: array(
)
)
1: Stmt_Echo(
exprs: array(
0: Scalar_LNumber(
value: 1
)
)
)
)
-----
<?php
namespace A {}
declare(ticks=1);
foo();
namespace B {}
-----
No code may exist outside of namespace {} on line 3
No code may exist outside of namespace {} from 3:1 to 3:17
array(
0: Stmt_Namespace(
name: Name(
parts: array(
0: A
)
)
stmts: array(
)
)
1: Stmt_Declare(
declares: array(
0: Stmt_DeclareDeclare(
key: ticks
value: Scalar_LNumber(
value: 1
)
)
)
stmts: null
)
2: Expr_FuncCall(
name: Name(
parts: array(
0: foo
)
)
args: array(
)
)
3: Stmt_Namespace(
name: Name(
parts: array(
0: B
)
)
stmts: array(
)
)
)

Some files were not shown because too many files have changed in this diff Show More