Compare commits

...

32 Commits

Author SHA1 Message Date
Markus Staab
c9d0b6c9f2
Support WeakReferences in NodeConnectingVisitor (#1057)
Add a constructor argument which enables the use of WeakReferences.
The attributes are called weak_parent, weak_next and weak_previous
in that case.
2025-02-26 20:51:23 +01:00
Markus Staab
14f9c9df7f Use more precise list-types for getter methods 2025-02-18 22:17:03 +01:00
Abdul Malik Ikhsan
fa02db3f30
Remove useless foreach key and cast string, and fix invalid @var definition in test (#1061) 2025-01-17 22:41:39 +01:00
Abdul Malik Ikhsan
d4fce83c2c Remove useless cast (string) and @var doc 2025-01-16 21:27:18 +01:00
Nikita Popov
7d3039c378 Update to phpstan 2.0 2024-12-30 12:14:29 +01:00
Nikita Popov
447a020a1f Release PHP-Parser 5.4.0 2024-12-30 12:07:19 +01:00
Markus Staab
45f70ed80a Simplify NodeTraverser->traverseNode() 2024-12-30 11:10:07 +01:00
Markus Staab
74a361814d Simplify NodeTraverser->traverseArray() 2024-12-30 11:10:07 +01:00
Nikita Popov
6478c5ac53 Fix PropertyHook::getStmts() for set hook
Produce the correct desugaring if the propertyName attribute is
set, and set it in the parser. Otherwise throw an exception.

Fixes #1053.
2024-12-27 16:56:00 +01:00
Nikita Popov
8bb415902e Fix cs 2024-12-27 16:33:38 +01:00
Nikita Popov
48fd76e542 Make PropertyHook::getStmts() less incorrect
Still missing the assignment to the correct property, but at
least we're not returning from a non-void function now...
2024-12-27 16:30:03 +01:00
Ondrej Mirtes
d20a197ca7 Emit error - Multiple properties cannot share the same hooks
Closes GH-1052.
2024-12-27 16:19:46 +01:00
Ondrej Mirtes
62dee28027 Rename ParserAbstract method checkPropertyHookList to checkEmptyPropertyHookList 2024-12-27 16:13:47 +01:00
Nikita Popov
b396e9e0d6 Add missing newline after usage message 2024-12-27 16:13:29 +01:00
Ondrej Mirtes
f212bb7afb Add PropertyHook::isFinal() helper method with tests 2024-12-13 23:15:12 +01:00
Ondrej Mirtes
f43324a074 Missing flags subNode description in PropertyHook constructor PHPDoc 2024-12-13 23:15:12 +01:00
Ondrej Mirtes
73b160f8c4 Add flags helper methods Property::isAbstract() and Property::isFinal() 2024-12-13 23:15:12 +01:00
Ondrej Mirtes
4f9dc8b0f5 Promoted properties with hooks do not need visibility modifier 2024-12-11 10:17:55 +01:00
Nathanael Esayeas
469377f4a8
Fix classmap authoritative autoloading (#1046)
This patch resolves #1045 by adding dummy class definitions
behind if (false) checks that are picked up the authoritative
autoloader.
2024-12-06 22:00:17 +01:00
Jorg Sowa
74d3f7fc24 Removed reudndant token generation 2024-11-24 09:54:02 +01:00
Jorg Sowa
25828ea952 Updated target.php to new PHP-Parser version 2024-11-24 09:54:02 +01:00
Jorg Sowa
05ed79595b Removed unrecheable statement 2024-11-19 21:17:27 +01:00
Markus Staab
954f7a411f More precise toLowerString() return type 2024-11-05 21:19:09 +01:00
Nikita Popov
8eea230464 Release PHP-Parser 5.3.1 2024-10-08 20:51:32 +02:00
Nikita Popov
9c7a3f8d8f Support declaring functions with name exit/die
For use in stubs.

Fixes #1030.
2024-10-08 20:46:50 +02:00
Markus Staab
26573ea64f Makefile works on windows 2024-10-07 20:54:36 +02:00
Nikita Popov
3abf7425cd Release PHP-Parser 5.3.0 2024-09-29 15:56:26 +02:00
Nikita Popov
961f158f6d Avoid negative indendation in formatting-preserving printer
Fixes #1015.
2024-09-29 15:34:59 +02:00
Nikita Popov
e50c67b7a9 Add basic support for tab indentation
Add a new "indent" option for the pretty printer, which can be
use to control the indentation width, or switch it to use tabs.

Tab width is currenlty hardcoded to 4, but also shouldn't matter
much.

Possibly the formatting-preserving printer should auto-detect
the indentation in the future.
2024-09-21 20:58:10 +02:00
Nikita Popov
26a0197186 Use PHP 8.4 for the PHP 7.4 integration test 2024-09-21 16:03:21 +02:00
Nikita Popov
aedfcc23cd Include trailing semicolon in GroupUse
Fixes https://github.com/nikic/PHP-Parser/issues/1026.
2024-09-21 15:55:21 +02:00
Nikita Popov
8d09ba87f4 Support PropertyHooks in NameResolver 2024-09-17 21:33:51 +02:00
54 changed files with 3363 additions and 2441 deletions

View File

@ -56,7 +56,7 @@ jobs:
run: "php vendor/bin/phpunit"
test_old_73_80:
runs-on: "ubuntu-latest"
name: "PHP 7.4 Code on PHP 8.3 Integration Tests"
name: "PHP 7.4 Code on PHP 8.4 Integration Tests"
steps:
- name: "Checkout"
uses: "actions/checkout@v4"
@ -64,7 +64,7 @@ jobs:
uses: "shivammathur/setup-php@v2"
with:
coverage: "none"
php-version: "8.3"
php-version: "8.4"
ini-file: "development"
tools: composer:v2
- name: "Install PHP 8 dependencies"

View File

@ -1,3 +1,46 @@
Version 5.4.0 (2024-12-30)
--------------------------
### Added
* Added `Property::isAbstract()` and `Property::isFinal()` methods.
* Added `PropertyHook::isFinal()` method.
* Emit an error if property hook is used on declaration with multiple properties.
### Fixed
* Make legacy class aliases compatible with classmap-authoritative autoloader.
* `Param::isPromoted()` and `Param::isPublic()` now returns true for parameters that have property
hooks but no explicit visibility modifier.
* `PropertyHook::getStmts()` now correctly desugars short `set` hooks. `set => $value` will be
expanded to `set { $this->propertyName = $value; }`. This requires the `propertyName` attribute
on the hook to be set, which is now also set by the parser. If the attribute is not set,
`getStmts()` will throw an error for short set hooks, as it is not possible to produce a correct
desugaring.
Version 5.3.1 (2024-10-08)
--------------------------
### Added
* Added support for declaring functions with name `exit` or `die`, to allow their use in stubs.
Version 5.3.0 (2024-09-29)
--------------------------
### Added
* Added `indent` option to pretty printer, which can be used to specify the indentation to use
(defaulting to four spaces). This also allows using tab indentation.
### Fixed
* Resolve names in `PropertyHook`s in the `NameResolver`.
* Include the trailing semicolon inside `Stmt\GroupUse` nodes, making them consistent with
`Stmt\Use_` nodes.
* Fixed indentation sometimes becoming negative in formatting-preserving pretty printer, resulting
in `ValueError`s.
Version 5.2.0 (2024-09-15)
--------------------------

View File

@ -4,7 +4,10 @@ tools/vendor:
composer install -d tools
phpstan: tools/vendor
tools/vendor/bin/phpstan
php tools/vendor/bin/phpstan
php-cs-fixer: tools/vendor
tools/vendor/bin/php-cs-fixer fix
php tools/vendor/bin/php-cs-fixer fix
tests:
php vendor/bin/phpunit

View File

@ -51,3 +51,17 @@ obtained through `$node->getAttribute('next')`.
`ParentConnectingVisitor` and `NodeConnectingVisitor` should not be used at the same time. The latter
includes the functionality of the former.
How can I limit the impact of cyclic references in the AST?
-----
NodeConnectingVisitor adds a parent reference, which introduces a cycle. This means that the AST can now only be collected by the cycle garbage collector.
This in turn can lead to performance and/or memory issues.
To break the cyclic references between AST nodes `NodeConnectingVisitor` supports a boolean `$weakReferences` constructor parameter.
When set to `true`, all attributes added by `NodeConnectingVisitor` will be wrapped into a `WeakReference` object.
After enabling this parameter, the parent node can be obtained through `$node->getAttribute('weak_parent')`,
the previous node can be obtained through `$node->getAttribute('weak_previous')`, and the next node can be
obtained through `$node->getAttribute('weak_next')`.

View File

@ -37,6 +37,7 @@ integer should be printed as decimal, hexadecimal, etc). Additionally, it suppor
* `phpVersion` (defaults to 7.4) allows opting into formatting that is not supported by older PHP
versions.
* `newline` (defaults to `"\n"`) can be set to `"\r\n"` in order to produce Windows newlines.
* `indent` (defaults to four spaces `" "`) can be set to any number of spaces or a single tab.
* `shortArraySyntax` determines the used array syntax if the `kind` attribute is not set. This is
a legacy option, and `phpVersion` should be used to control this behavior instead.

View File

@ -256,7 +256,7 @@ top_statement:
$this->checkNamespace($$); }
| T_USE use_declarations semi { $$ = Stmt\Use_[$2, Stmt\Use_::TYPE_NORMAL]; }
| T_USE use_type use_declarations semi { $$ = Stmt\Use_[$3, $2]; }
| group_use_declaration semi
| group_use_declaration
| T_CONST constant_declaration_list semi { $$ = Stmt\Const_[$2]; }
;
@ -266,9 +266,9 @@ use_type:
;
group_use_declaration:
T_USE use_type legacy_namespace_name T_NS_SEPARATOR '{' unprefixed_use_declarations '}'
T_USE use_type legacy_namespace_name T_NS_SEPARATOR '{' unprefixed_use_declarations '}' semi
{ $$ = Stmt\GroupUse[$3, $6, $2]; }
| T_USE legacy_namespace_name T_NS_SEPARATOR '{' inline_use_declarations '}'
| T_USE legacy_namespace_name T_NS_SEPARATOR '{' inline_use_declarations '}' semi
{ $$ = Stmt\GroupUse[$2, $5, Stmt\Use_::TYPE_UNKNOWN]; }
;
@ -464,15 +464,16 @@ block_or_error:
| error { $$ = []; }
;
identifier_maybe_readonly:
fn_identifier:
identifier_not_reserved
| T_READONLY { $$ = Node\Identifier[$1]; }
| T_EXIT { $$ = Node\Identifier[$1]; }
;
function_declaration_statement:
T_FUNCTION optional_ref identifier_maybe_readonly '(' parameter_list ')' optional_return_type block_or_error
T_FUNCTION optional_ref fn_identifier '(' parameter_list ')' optional_return_type block_or_error
{ $$ = Stmt\Function_[$3, ['byRef' => $2, 'params' => $5, 'returnType' => $7, 'stmts' => $8, 'attrGroups' => []]]; }
| attributes T_FUNCTION optional_ref identifier_maybe_readonly '(' parameter_list ')' optional_return_type block_or_error
| attributes T_FUNCTION optional_ref fn_identifier '(' parameter_list ')' optional_return_type block_or_error
{ $$ = Stmt\Function_[$4, ['byRef' => $3, 'params' => $6, 'returnType' => $8, 'stmts' => $9, 'attrGroups' => $1]]; }
;
@ -685,11 +686,13 @@ parameter:
optional_attributes optional_property_modifiers optional_type_without_static
optional_arg_ref optional_ellipsis plain_variable optional_property_hook_list
{ $$ = new Node\Param($6, null, $3, $4, $5, attributes(), $2, $1, $7);
$this->checkParam($$); }
$this->checkParam($$);
$this->addPropertyNameToHooks($$); }
| optional_attributes optional_property_modifiers optional_type_without_static
optional_arg_ref optional_ellipsis plain_variable '=' expr optional_property_hook_list
{ $$ = new Node\Param($6, $8, $3, $4, $5, attributes(), $2, $1, $9);
$this->checkParam($$); }
$this->checkParam($$);
$this->addPropertyNameToHooks($$); }
| optional_attributes optional_property_modifiers optional_type_without_static
optional_arg_ref optional_ellipsis error
{ $$ = new Node\Param(Expr\Error[], null, $3, $4, $5, attributes(), $2, $1); }
@ -839,7 +842,9 @@ class_statement:
#if PHP8
| optional_attributes variable_modifiers optional_type_without_static property_declaration_list '{' property_hook_list '}'
{ $$ = new Stmt\Property($2, $4, attributes(), $3, $1, $6);
$this->checkPropertyHookList($6, #5); }
$this->checkPropertyHooksForMultiProperty($$, #5);
$this->checkEmptyPropertyHookList($6, #5);
$this->addPropertyNameToHooks($$); }
#endif
| optional_attributes method_modifiers T_CONST class_const_list semi
{ $$ = new Stmt\ClassConst($4, $2, attributes(), $1);
@ -948,7 +953,7 @@ property_hook_list:
optional_property_hook_list:
/* empty */ { $$ = []; }
#if PHP8
| '{' property_hook_list '}' { $$ = $2; $this->checkPropertyHookList($2, #1); }
| '{' property_hook_list '}' { $$ = $2; $this->checkEmptyPropertyHookList($2, #1); }
#endif
;

View File

@ -20,9 +20,9 @@ class TokenStream {
*
* @param Token[] $tokens Tokens in PhpToken::tokenize() format
*/
public function __construct(array $tokens) {
public function __construct(array $tokens, int $tabWidth) {
$this->tokens = $tokens;
$this->indentMap = $this->calcIndentMap();
$this->indentMap = $this->calcIndentMap($tabWidth);
}
/**
@ -248,7 +248,7 @@ class TokenStream {
*
* @return int[] Token position to indentation map
*/
private function calcIndentMap(): array {
private function calcIndentMap(int $tabWidth): array {
$indentMap = [];
$indent = 0;
foreach ($this->tokens as $i => $token) {
@ -258,11 +258,11 @@ class TokenStream {
$content = $token->text;
$newlinePos = \strrpos($content, "\n");
if (false !== $newlinePos) {
$indent = \strlen($content) - $newlinePos - 1;
$indent = $this->getIndent(\substr($content, $newlinePos + 1), $tabWidth);
} elseif ($i === 1 && $this->tokens[0]->id === \T_OPEN_TAG &&
$this->tokens[0]->text[\strlen($this->tokens[0]->text) - 1] === "\n") {
// Special case: Newline at the end of opening tag followed by whitespace.
$indent = \strlen($content);
$indent = $this->getIndent($content, $tabWidth);
}
}
}
@ -272,4 +272,11 @@ class TokenStream {
return $indentMap;
}
private function getIndent(string $ws, int $tabWidth): int {
$spaces = \substr_count($ws, " ");
$tabs = \substr_count($ws, "\t");
assert(\strlen($ws) === $spaces + $tabs);
return $spaces + $tabs * $tabWidth;
}
}

View File

@ -1,3 +1,11 @@
<?php declare(strict_types=1);
namespace PhpParser\Node\Expr;
require __DIR__ . '/../ArrayItem.php';
if (false) {
// For classmap-authoritative support.
class ArrayItem extends \PhpParser\Node\ArrayItem {
}
}

View File

@ -1,3 +1,11 @@
<?php declare(strict_types=1);
namespace PhpParser\Node\Expr;
require __DIR__ . '/../ClosureUse.php';
if (false) {
// For classmap-authoritative support.
class ClosureUse extends \PhpParser\Node\ClosureUse {
}
}

View File

@ -53,7 +53,7 @@ class Identifier extends NodeAbstract {
/**
* Get lowercased identifier as string.
*
* @psalm-return non-empty-string
* @psalm-return non-empty-string&lowercase-string
* @return string Lowercased identifier as string
*/
public function toLowerString(): string {

View File

@ -8,7 +8,6 @@ use PhpParser\NodeAbstract;
class MatchArm extends NodeAbstract {
/** @var null|list<Node\Expr> */
public ?array $conds;
/** @var Node\Expr */
public Expr $body;
/**

View File

@ -129,7 +129,7 @@ class Name extends NodeAbstract {
* Returns lowercased string representation of the name, without taking the name type into
* account (e.g., no leading backslash for fully qualified names).
*
* @psalm-return non-empty-string
* @psalm-return non-empty-string&lowercase-string
* @return string Lowercased string representation
*/
public function toLowerString(): string {

View File

@ -68,11 +68,20 @@ class Param extends NodeAbstract {
* Whether this parameter uses constructor property promotion.
*/
public function isPromoted(): bool {
return $this->flags !== 0;
return $this->flags !== 0 || $this->hooks !== [];
}
public function isPublic(): bool {
return (bool) ($this->flags & Modifiers::PUBLIC);
$public = (bool) ($this->flags & Modifiers::PUBLIC);
if ($public) {
return true;
}
if ($this->hooks === []) {
return false;
}
return ($this->flags & Modifiers::VISIBILITY_MASK) === 0;
}
public function isProtected(): bool {

View File

@ -2,6 +2,11 @@
namespace PhpParser\Node;
use PhpParser\Modifiers;
use PhpParser\Node\Expr\Assign;
use PhpParser\Node\Expr\PropertyFetch;
use PhpParser\Node\Expr\Variable;
use PhpParser\Node\Stmt\Expression;
use PhpParser\Node\Stmt\Return_;
use PhpParser\NodeAbstract;
@ -30,6 +35,7 @@ class PropertyHook extends NodeAbstract implements FunctionLike {
* params?: Param[],
* attrGroups?: AttributeGroup[],
* } $subNodes Array of the following optional subnodes:
* 'flags => 0 : Flags
* 'byRef' => false : Whether hook returns by reference
* 'params' => array(): Parameters
* 'attrGroups' => array(): PHP attribute groups
@ -57,9 +63,30 @@ class PropertyHook extends NodeAbstract implements FunctionLike {
return null;
}
/**
* Whether the property hook is final.
*/
public function isFinal(): bool {
return (bool) ($this->flags & Modifiers::FINAL);
}
public function getStmts(): ?array {
if ($this->body instanceof Expr) {
return [new Return_($this->body)];
$name = $this->name->toLowerString();
if ($name === 'get') {
return [new Return_($this->body)];
}
if ($name === 'set') {
if (!$this->hasAttribute('propertyName')) {
throw new \LogicException(
'Can only use getStmts() on a "set" hook if the "propertyName" attribute is set');
}
$propName = $this->getAttribute('propertyName');
$prop = new PropertyFetch(new Variable('this'), (string) $propName);
return [new Expression(new Assign($prop, $this->body))];
}
throw new \LogicException('Unknown property hook "' . $name . '"');
}
return $this->body;
}

View File

@ -1,3 +1,11 @@
<?php declare(strict_types=1);
namespace PhpParser\Node\Scalar;
require __DIR__ . '/Float_.php';
if (false) {
// For classmap-authoritative support.
class DNumber extends Float_ {
}
}

View File

@ -1,3 +1,11 @@
<?php declare(strict_types=1);
namespace PhpParser\Node\Scalar;
require __DIR__ . '/InterpolatedString.php';
if (false) {
// For classmap-authoritative support.
class Encapsed extends InterpolatedString {
}
}

View File

@ -1,3 +1,13 @@
<?php declare(strict_types=1);
namespace PhpParser\Node\Scalar;
use PhpParser\Node\InterpolatedStringPart;
require __DIR__ . '/../InterpolatedStringPart.php';
if (false) {
// For classmap-authoritative support.
class EncapsedStringPart extends InterpolatedStringPart {
}
}

View File

@ -1,3 +1,11 @@
<?php declare(strict_types=1);
namespace PhpParser\Node\Scalar;
require __DIR__ . '/Int_.php';
if (false) {
// For classmap-authoritative support.
class LNumber extends Int_ {
}
}

View File

@ -17,7 +17,7 @@ abstract class ClassLike extends Node\Stmt {
public ?Node\Name $namespacedName;
/**
* @return TraitUse[]
* @return list<TraitUse>
*/
public function getTraitUses(): array {
$traitUses = [];
@ -30,7 +30,7 @@ abstract class ClassLike extends Node\Stmt {
}
/**
* @return ClassConst[]
* @return list<ClassConst>
*/
public function getConstants(): array {
$constants = [];
@ -43,7 +43,7 @@ abstract class ClassLike extends Node\Stmt {
}
/**
* @return Property[]
* @return list<Property>
*/
public function getProperties(): array {
$properties = [];
@ -78,7 +78,7 @@ abstract class ClassLike extends Node\Stmt {
/**
* Gets all methods defined directly in this class/interface/trait
*
* @return ClassMethod[]
* @return list<ClassMethod>
*/
public function getMethods(): array {
$methods = [];

View File

@ -1,3 +1,13 @@
<?php declare(strict_types=1);
namespace PhpParser\Node\Stmt;
use PhpParser\Node\DeclareItem;
require __DIR__ . '/../DeclareItem.php';
if (false) {
// For classmap-authoritative support.
class DeclareDeclare extends DeclareItem {
}
}

View File

@ -80,6 +80,20 @@ class Property extends Node\Stmt {
return (bool) ($this->flags & Modifiers::READONLY);
}
/**
* Whether the property is abstract.
*/
public function isAbstract(): bool {
return (bool) ($this->flags & Modifiers::ABSTRACT);
}
/**
* Whether the property is final.
*/
public function isFinal(): bool {
return (bool) ($this->flags & Modifiers::FINAL);
}
/**
* Whether the property has explicit public(set) visibility.
*/

View File

@ -1,3 +1,13 @@
<?php declare(strict_types=1);
namespace PhpParser\Node\Stmt;
use PhpParser\Node\PropertyItem;
require __DIR__ . '/../PropertyItem.php';
if (false) {
// For classmap-authoritative support.
class PropertyProperty extends PropertyItem {
}
}

View File

@ -1,3 +1,11 @@
<?php declare(strict_types=1);
namespace PhpParser\Node\Stmt;
require __DIR__ . '/../StaticVar.php';
if (false) {
// For classmap-authoritative support.
class StaticVar extends \PhpParser\Node\StaticVar {
}
}

View File

@ -1,3 +1,13 @@
<?php declare(strict_types=1);
namespace PhpParser\Node\Stmt;
use PhpParser\Node\UseItem;
require __DIR__ . '/../UseItem.php';
if (false) {
// For classmap-authoritative support.
class UseUse extends UseItem {
}
}

View File

@ -145,7 +145,7 @@ class NodeDumper {
} elseif ($node instanceof Comment) {
$this->res .= \str_replace("\n", $this->nl, $node->getReformattedText());
} elseif (\is_string($node)) {
$this->res .= \str_replace("\n", $this->nl, (string)$node);
$this->res .= \str_replace("\n", $this->nl, $node);
} elseif (\is_int($node) || \is_float($node)) {
$this->res .= $node;
} elseif (null === $node) {

View File

@ -99,66 +99,72 @@ class NodeTraverser implements NodeTraverserInterface {
if ($this->stopTraversal) {
break;
}
} elseif ($subNode instanceof Node) {
$traverseChildren = true;
$visitorIndex = -1;
foreach ($this->visitors as $visitorIndex => $visitor) {
$return = $visitor->enterNode($subNode);
if (null !== $return) {
if ($return instanceof Node) {
$this->ensureReplacementReasonable($subNode, $return);
$subNode = $node->$name = $return;
} elseif (NodeVisitor::DONT_TRAVERSE_CHILDREN === $return) {
$traverseChildren = false;
} elseif (NodeVisitor::DONT_TRAVERSE_CURRENT_AND_CHILDREN === $return) {
$traverseChildren = false;
break;
} elseif (NodeVisitor::STOP_TRAVERSAL === $return) {
$this->stopTraversal = true;
break 2;
} elseif (NodeVisitor::REPLACE_WITH_NULL === $return) {
$node->$name = null;
continue 2;
} else {
throw new \LogicException(
'enterNode() returned invalid value of type ' . gettype($return)
);
}
}
}
continue;
}
if ($traverseChildren) {
$this->traverseNode($subNode);
if ($this->stopTraversal) {
if (!$subNode instanceof Node) {
continue;
}
$traverseChildren = true;
$visitorIndex = -1;
foreach ($this->visitors as $visitorIndex => $visitor) {
$return = $visitor->enterNode($subNode);
if (null !== $return) {
if ($return instanceof Node) {
$this->ensureReplacementReasonable($subNode, $return);
$subNode = $node->$name = $return;
} elseif (NodeVisitor::DONT_TRAVERSE_CHILDREN === $return) {
$traverseChildren = false;
} elseif (NodeVisitor::DONT_TRAVERSE_CURRENT_AND_CHILDREN === $return) {
$traverseChildren = false;
break;
} elseif (NodeVisitor::STOP_TRAVERSAL === $return) {
$this->stopTraversal = true;
break 2;
} elseif (NodeVisitor::REPLACE_WITH_NULL === $return) {
$node->$name = null;
continue 2;
} else {
throw new \LogicException(
'enterNode() returned invalid value of type ' . gettype($return)
);
}
}
}
for (; $visitorIndex >= 0; --$visitorIndex) {
$visitor = $this->visitors[$visitorIndex];
$return = $visitor->leaveNode($subNode);
if ($traverseChildren) {
$this->traverseNode($subNode);
if ($this->stopTraversal) {
break;
}
}
if (null !== $return) {
if ($return instanceof Node) {
$this->ensureReplacementReasonable($subNode, $return);
$subNode = $node->$name = $return;
} elseif (NodeVisitor::STOP_TRAVERSAL === $return) {
$this->stopTraversal = true;
break 2;
} elseif (NodeVisitor::REPLACE_WITH_NULL === $return) {
$node->$name = null;
break;
} elseif (\is_array($return)) {
throw new \LogicException(
'leaveNode() may only return an array ' .
'if the parent structure is an array'
);
} else {
throw new \LogicException(
'leaveNode() returned invalid value of type ' . gettype($return)
);
}
for (; $visitorIndex >= 0; --$visitorIndex) {
$visitor = $this->visitors[$visitorIndex];
$return = $visitor->leaveNode($subNode);
if (null !== $return) {
if ($return instanceof Node) {
$this->ensureReplacementReasonable($subNode, $return);
$subNode = $node->$name = $return;
} elseif (NodeVisitor::STOP_TRAVERSAL === $return) {
$this->stopTraversal = true;
break 2;
} elseif (NodeVisitor::REPLACE_WITH_NULL === $return) {
$node->$name = null;
break;
} elseif (\is_array($return)) {
throw new \LogicException(
'leaveNode() may only return an array ' .
'if the parent structure is an array'
);
} else {
throw new \LogicException(
'leaveNode() returned invalid value of type ' . gettype($return)
);
}
}
}
@ -176,77 +182,80 @@ class NodeTraverser implements NodeTraverserInterface {
$doNodes = [];
foreach ($nodes as $i => $node) {
if ($node instanceof Node) {
$traverseChildren = true;
$visitorIndex = -1;
foreach ($this->visitors as $visitorIndex => $visitor) {
$return = $visitor->enterNode($node);
if (null !== $return) {
if ($return instanceof Node) {
$this->ensureReplacementReasonable($node, $return);
$nodes[$i] = $node = $return;
} elseif (\is_array($return)) {
$doNodes[] = [$i, $return];
continue 2;
} elseif (NodeVisitor::REMOVE_NODE === $return) {
$doNodes[] = [$i, []];
continue 2;
} elseif (NodeVisitor::DONT_TRAVERSE_CHILDREN === $return) {
$traverseChildren = false;
} elseif (NodeVisitor::DONT_TRAVERSE_CURRENT_AND_CHILDREN === $return) {
$traverseChildren = false;
break;
} elseif (NodeVisitor::STOP_TRAVERSAL === $return) {
$this->stopTraversal = true;
break 2;
} elseif (NodeVisitor::REPLACE_WITH_NULL === $return) {
throw new \LogicException(
'REPLACE_WITH_NULL can not be used if the parent structure is an array');
} else {
throw new \LogicException(
'enterNode() returned invalid value of type ' . gettype($return)
);
}
}
if (!$node instanceof Node) {
if (\is_array($node)) {
throw new \LogicException('Invalid node structure: Contains nested arrays');
}
continue;
}
if ($traverseChildren) {
$this->traverseNode($node);
if ($this->stopTraversal) {
$traverseChildren = true;
$visitorIndex = -1;
foreach ($this->visitors as $visitorIndex => $visitor) {
$return = $visitor->enterNode($node);
if (null !== $return) {
if ($return instanceof Node) {
$this->ensureReplacementReasonable($node, $return);
$nodes[$i] = $node = $return;
} elseif (\is_array($return)) {
$doNodes[] = [$i, $return];
continue 2;
} elseif (NodeVisitor::REMOVE_NODE === $return) {
$doNodes[] = [$i, []];
continue 2;
} elseif (NodeVisitor::DONT_TRAVERSE_CHILDREN === $return) {
$traverseChildren = false;
} elseif (NodeVisitor::DONT_TRAVERSE_CURRENT_AND_CHILDREN === $return) {
$traverseChildren = false;
break;
} elseif (NodeVisitor::STOP_TRAVERSAL === $return) {
$this->stopTraversal = true;
break 2;
} elseif (NodeVisitor::REPLACE_WITH_NULL === $return) {
throw new \LogicException(
'REPLACE_WITH_NULL can not be used if the parent structure is an array');
} else {
throw new \LogicException(
'enterNode() returned invalid value of type ' . gettype($return)
);
}
}
}
for (; $visitorIndex >= 0; --$visitorIndex) {
$visitor = $this->visitors[$visitorIndex];
$return = $visitor->leaveNode($node);
if ($traverseChildren) {
$this->traverseNode($node);
if ($this->stopTraversal) {
break;
}
}
if (null !== $return) {
if ($return instanceof Node) {
$this->ensureReplacementReasonable($node, $return);
$nodes[$i] = $node = $return;
} elseif (\is_array($return)) {
$doNodes[] = [$i, $return];
break;
} elseif (NodeVisitor::REMOVE_NODE === $return) {
$doNodes[] = [$i, []];
break;
} elseif (NodeVisitor::STOP_TRAVERSAL === $return) {
$this->stopTraversal = true;
break 2;
} elseif (NodeVisitor::REPLACE_WITH_NULL === $return) {
throw new \LogicException(
'REPLACE_WITH_NULL can not be used if the parent structure is an array');
} else {
throw new \LogicException(
'leaveNode() returned invalid value of type ' . gettype($return)
);
}
for (; $visitorIndex >= 0; --$visitorIndex) {
$visitor = $this->visitors[$visitorIndex];
$return = $visitor->leaveNode($node);
if (null !== $return) {
if ($return instanceof Node) {
$this->ensureReplacementReasonable($node, $return);
$nodes[$i] = $node = $return;
} elseif (\is_array($return)) {
$doNodes[] = [$i, $return];
break;
} elseif (NodeVisitor::REMOVE_NODE === $return) {
$doNodes[] = [$i, []];
break;
} elseif (NodeVisitor::STOP_TRAVERSAL === $return) {
$this->stopTraversal = true;
break 2;
} elseif (NodeVisitor::REPLACE_WITH_NULL === $return) {
throw new \LogicException(
'REPLACE_WITH_NULL can not be used if the parent structure is an array');
} else {
throw new \LogicException(
'leaveNode() returned invalid value of type ' . gettype($return)
);
}
}
} elseif (\is_array($node)) {
throw new \LogicException('Invalid node structure: Contains nested arrays');
}
}

View File

@ -12,7 +12,7 @@ use PhpParser\NodeVisitorAbstract;
class FindingVisitor extends NodeVisitorAbstract {
/** @var callable Filter callback */
protected $filterCallback;
/** @var Node[] Found nodes */
/** @var list<Node> Found nodes */
protected array $foundNodes;
public function __construct(callable $filterCallback) {
@ -24,7 +24,7 @@ class FindingVisitor extends NodeVisitorAbstract {
*
* Nodes are returned in pre-order.
*
* @return Node[] Found nodes
* @return list<Node> Found nodes
*/
public function getFoundNodes(): array {
return $this->foundNodes;

View File

@ -110,6 +110,12 @@ class NameResolver extends NodeVisitorAbstract {
$node->type = $this->resolveType($node->type);
}
$this->resolveAttrGroups($node);
} elseif ($node instanceof Node\PropertyHook) {
foreach ($node->params as $param) {
$param->type = $this->resolveType($param->type);
$this->resolveAttrGroups($param);
}
$this->resolveAttrGroups($node);
} elseif ($node instanceof Stmt\Const_) {
foreach ($node->consts as $const) {
$this->addNamespacedName($const);

View File

@ -9,10 +9,12 @@ use PhpParser\NodeVisitorAbstract;
* Visitor that connects a child node to its parent node
* as well as its sibling nodes.
*
* On the child node, the parent node can be accessed through
* With <code>$weakReferences=false</code> on the child node, the parent node can be accessed through
* <code>$node->getAttribute('parent')</code>, the previous
* node can be accessed through <code>$node->getAttribute('previous')</code>,
* and the next node can be accessed through <code>$node->getAttribute('next')</code>.
*
* With <code>$weakReferences=true</code> attribute names are prefixed by "weak_", e.g. "weak_parent".
*/
final class NodeConnectingVisitor extends NodeVisitorAbstract {
/**
@ -25,6 +27,12 @@ final class NodeConnectingVisitor extends NodeVisitorAbstract {
*/
private $previous;
private bool $weakReferences;
public function __construct(bool $weakReferences = false) {
$this->weakReferences = $weakReferences;
}
public function beforeTraverse(array $nodes) {
$this->stack = [];
$this->previous = null;
@ -32,12 +40,26 @@ final class NodeConnectingVisitor extends NodeVisitorAbstract {
public function enterNode(Node $node) {
if (!empty($this->stack)) {
$node->setAttribute('parent', $this->stack[count($this->stack) - 1]);
$parent = $this->stack[count($this->stack) - 1];
if ($this->weakReferences) {
$node->setAttribute('weak_parent', \WeakReference::create($parent));
} else {
$node->setAttribute('parent', $parent);
}
}
if ($this->previous !== null && $this->previous->getAttribute('parent') === $node->getAttribute('parent')) {
$node->setAttribute('previous', $this->previous);
$this->previous->setAttribute('next', $node);
if ($this->previous !== null) {
if (
$this->weakReferences
) {
if ($this->previous->getAttribute('weak_parent') === $node->getAttribute('weak_parent')) {
$node->setAttribute('weak_previous', \WeakReference::create($this->previous));
$this->previous->setAttribute('weak_next', \WeakReference::create($node));
}
} elseif ($this->previous->getAttribute('parent') === $node->getAttribute('parent')) {
$node->setAttribute('previous', $this->previous);
$this->previous->setAttribute('next', $node);
}
}
$this->stack[] = $node;

View File

@ -11,8 +11,10 @@ use function count;
/**
* Visitor that connects a child node to its parent node.
*
* On the child node, the parent node can be accessed through
* With <code>$weakReferences=false</code> on the child node, the parent node can be accessed through
* <code>$node->getAttribute('parent')</code>.
*
* With <code>$weakReferences=true</code> the attribute name is "weak_parent" instead.
*/
final class ParentConnectingVisitor extends NodeVisitorAbstract {
/**
@ -20,13 +22,24 @@ final class ParentConnectingVisitor extends NodeVisitorAbstract {
*/
private array $stack = [];
private bool $weakReferences;
public function __construct(bool $weakReferences = false) {
$this->weakReferences = $weakReferences;
}
public function beforeTraverse(array $nodes) {
$this->stack = [];
}
public function enterNode(Node $node) {
if (!empty($this->stack)) {
$node->setAttribute('parent', $this->stack[count($this->stack) - 1]);
$parent = $this->stack[count($this->stack) - 1];
if ($this->weakReferences) {
$node->setAttribute('weak_parent', \WeakReference::create($parent));
} else {
$node->setAttribute('parent', $parent);
}
}
$this->stack[] = $node;

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -32,6 +32,7 @@ use PhpParser\Node\Stmt\Nop;
use PhpParser\Node\Stmt\Property;
use PhpParser\Node\Stmt\TryCatch;
use PhpParser\Node\UseItem;
use PhpParser\Node\VarLikeIdentifier;
use PhpParser\NodeVisitor\CommentAnnotatingVisitor;
abstract class ParserAbstract implements Parser {
@ -411,8 +412,6 @@ abstract class ParserAbstract implements Parser {
$rule = $state - $this->numNonLeafStates;
}
}
throw new \RuntimeException('Reached end of parser loop');
}
protected function emitError(Error $error): void {
@ -1160,8 +1159,15 @@ abstract class ParserAbstract implements Parser {
}
}
protected function checkPropertyHooksForMultiProperty(Property $property, int $hookPos): void {
if (count($property->props) > 1) {
$this->emitError(new Error(
'Cannot use hooks when declaring multiple properties', $this->getAttributesAt($hookPos)));
}
}
/** @param PropertyHook[] $hooks */
protected function checkPropertyHookList(array $hooks, int $hookPos): void {
protected function checkEmptyPropertyHookList(array $hooks, int $hookPos): void {
if (empty($hooks)) {
$this->emitError(new Error(
'Property hook list cannot be empty', $this->getAttributesAt($hookPos)));
@ -1196,6 +1202,20 @@ abstract class ParserAbstract implements Parser {
}
}
/**
* @param Property|Param $node
*/
protected function addPropertyNameToHooks(Node $node): void {
if ($node instanceof Property) {
$name = $node->props[0]->name->toString();
} else {
$name = $node->var->name;
}
foreach ($node->hooks as $hook) {
$hook->setAttribute('propertyName', $name);
}
}
/** @param array<Node\Arg|Node\VariadicPlaceholder> $args */
private function isSimpleExit(array $args): bool {
if (\count($args) === 0) {

View File

@ -106,6 +106,15 @@ abstract class PrettyPrinterAbstract implements PrettyPrinter {
/** @var int Current indentation level. */
protected int $indentLevel;
/** @var string String for single level of indentation */
private string $indent;
/** @var int Width in spaces to indent by. */
private int $indentWidth;
/** @var bool Whether to use tab indentation. */
private bool $useTabs;
/** @var int Width in spaces of one tab. */
private int $tabWidth = 4;
/** @var string Newline style. Does not include current indentation. */
protected string $newline;
/** @var string Newline including current indentation. */
@ -170,12 +179,14 @@ abstract class PrettyPrinterAbstract implements PrettyPrinter {
* PHP version while specifying an older target (but the result will
* of course not be compatible with the older version in that case).
* * string $newline: The newline style to use. Should be "\n" (default) or "\r\n".
* * string $indent: The indentation to use. Should either be all spaces or a single
* tab. Defaults to four spaces (" ").
* * bool $shortArraySyntax: Whether to use [] instead of array() as the default array
* syntax, if the node does not specify a format. Defaults to whether
* the phpVersion support short array syntax.
*
* @param array{
* phpVersion?: PhpVersion, newline?: string, shortArraySyntax?: bool
* phpVersion?: PhpVersion, newline?: string, indent?: string, shortArraySyntax?: bool
* } $options Dictionary of formatting options
*/
public function __construct(array $options = []) {
@ -190,6 +201,17 @@ abstract class PrettyPrinterAbstract implements PrettyPrinter {
$options['shortArraySyntax'] ?? $this->phpVersion->supportsShortArraySyntax();
$this->docStringEndToken =
$this->phpVersion->supportsFlexibleHeredoc() ? null : '_DOC_STRING_END_' . mt_rand();
$this->indent = $indent = $options['indent'] ?? ' ';
if ($indent === "\t") {
$this->useTabs = true;
$this->indentWidth = $this->tabWidth;
} elseif ($indent === \str_repeat(' ', \strlen($indent))) {
$this->useTabs = false;
$this->indentWidth = \strlen($indent);
} else {
throw new \LogicException('Option "indent" must either be all spaces or a single tab');
}
}
/**
@ -208,24 +230,29 @@ abstract class PrettyPrinterAbstract implements PrettyPrinter {
*/
protected function setIndentLevel(int $level): void {
$this->indentLevel = $level;
$this->nl = $this->newline . \str_repeat(' ', $level);
if ($this->useTabs) {
$tabs = \intdiv($level, $this->tabWidth);
$spaces = $level % $this->tabWidth;
$this->nl = $this->newline . \str_repeat("\t", $tabs) . \str_repeat(' ', $spaces);
} else {
$this->nl = $this->newline . \str_repeat(' ', $level);
}
}
/**
* Increase indentation level.
*/
protected function indent(): void {
$this->indentLevel += 4;
$this->nl .= ' ';
$this->indentLevel += $this->indentWidth;
$this->nl .= $this->indent;
}
/**
* Decrease indentation level.
*/
protected function outdent(): void {
assert($this->indentLevel >= 4);
$this->indentLevel -= 4;
$this->nl = $this->newline . str_repeat(' ', $this->indentLevel);
assert($this->indentLevel >= $this->indentWidth);
$this->setIndentLevel($this->indentLevel - $this->indentWidth);
}
/**
@ -537,7 +564,7 @@ abstract class PrettyPrinterAbstract implements PrettyPrinter {
$this->initializeModifierChangeMap();
$this->resetState();
$this->origTokens = new TokenStream($origTokens);
$this->origTokens = new TokenStream($origTokens, $this->tabWidth);
$this->preprocessNodes($stmts);
@ -709,7 +736,7 @@ abstract class PrettyPrinterAbstract implements PrettyPrinter {
$result .= $extraLeft;
$origIndentLevel = $this->indentLevel;
$this->setIndentLevel($this->origTokens->getIndentationBefore($subStartPos) + $indentAdjustment);
$this->setIndentLevel(max($this->origTokens->getIndentationBefore($subStartPos) + $indentAdjustment, 0));
// If it's the same node that was previously in this position, it certainly doesn't
// need fixup. It's important to check this here, because our fixup checks are more
@ -812,7 +839,7 @@ abstract class PrettyPrinterAbstract implements PrettyPrinter {
\assert($itemStartPos >= 0 && $itemEndPos >= 0 && $itemStartPos >= $pos);
$origIndentLevel = $this->indentLevel;
$lastElemIndentLevel = $this->origTokens->getIndentationBefore($itemStartPos) + $indentAdjustment;
$lastElemIndentLevel = max($this->origTokens->getIndentationBefore($itemStartPos) + $indentAdjustment, 0);
$this->setIndentLevel($lastElemIndentLevel);
$comments = $arrItem->getComments();

View File

@ -1,191 +1,259 @@
parameters:
ignoreErrors:
-
message: "#^Method PhpParser\\\\Builder\\\\ClassConst\\:\\:__construct\\(\\) has parameter \\$value with no value type specified in iterable type array\\.$#"
message: '#^Method PhpParser\\Builder\\ClassConst\:\:__construct\(\) has parameter \$value with no value type specified in iterable type array\.$#'
identifier: missingType.iterableValue
count: 1
path: lib/PhpParser/Builder/ClassConst.php
-
message: "#^Method PhpParser\\\\Builder\\\\ClassConst\\:\\:addConst\\(\\) has parameter \\$value with no value type specified in iterable type array\\.$#"
message: '#^Method PhpParser\\Builder\\ClassConst\:\:addConst\(\) has parameter \$value with no value type specified in iterable type array\.$#'
identifier: missingType.iterableValue
count: 1
path: lib/PhpParser/Builder/ClassConst.php
-
message: "#^Method PhpParser\\\\BuilderFactory\\:\\:args\\(\\) has parameter \\$args with no value type specified in iterable type array\\.$#"
message: '#^Method PhpParser\\BuilderFactory\:\:args\(\) has parameter \$args with no value type specified in iterable type array\.$#'
identifier: missingType.iterableValue
count: 1
path: lib/PhpParser/BuilderFactory.php
-
message: "#^Method PhpParser\\\\BuilderFactory\\:\\:attribute\\(\\) has parameter \\$args with no value type specified in iterable type array\\.$#"
message: '#^Method PhpParser\\BuilderFactory\:\:attribute\(\) has parameter \$args with no value type specified in iterable type array\.$#'
identifier: missingType.iterableValue
count: 1
path: lib/PhpParser/BuilderFactory.php
-
message: "#^Method PhpParser\\\\BuilderFactory\\:\\:classConst\\(\\) has parameter \\$value with no value type specified in iterable type array\\.$#"
message: '#^Method PhpParser\\BuilderFactory\:\:classConst\(\) has parameter \$value with no value type specified in iterable type array\.$#'
identifier: missingType.iterableValue
count: 1
path: lib/PhpParser/BuilderFactory.php
-
message: "#^Method PhpParser\\\\BuilderFactory\\:\\:funcCall\\(\\) has parameter \\$args with no value type specified in iterable type array\\.$#"
message: '#^Method PhpParser\\BuilderFactory\:\:funcCall\(\) has parameter \$args with no value type specified in iterable type array\.$#'
identifier: missingType.iterableValue
count: 1
path: lib/PhpParser/BuilderFactory.php
-
message: "#^Method PhpParser\\\\BuilderFactory\\:\\:methodCall\\(\\) has parameter \\$args with no value type specified in iterable type array\\.$#"
message: '#^Method PhpParser\\BuilderFactory\:\:methodCall\(\) has parameter \$args with no value type specified in iterable type array\.$#'
identifier: missingType.iterableValue
count: 1
path: lib/PhpParser/BuilderFactory.php
-
message: "#^Method PhpParser\\\\BuilderFactory\\:\\:new\\(\\) has parameter \\$args with no value type specified in iterable type array\\.$#"
message: '#^Method PhpParser\\BuilderFactory\:\:new\(\) has parameter \$args with no value type specified in iterable type array\.$#'
identifier: missingType.iterableValue
count: 1
path: lib/PhpParser/BuilderFactory.php
-
message: "#^Method PhpParser\\\\BuilderFactory\\:\\:staticCall\\(\\) has parameter \\$args with no value type specified in iterable type array\\.$#"
message: '#^Method PhpParser\\BuilderFactory\:\:staticCall\(\) has parameter \$args with no value type specified in iterable type array\.$#'
identifier: missingType.iterableValue
count: 1
path: lib/PhpParser/BuilderFactory.php
-
message: "#^Method PhpParser\\\\BuilderFactory\\:\\:val\\(\\) has parameter \\$value with no value type specified in iterable type array\\.$#"
message: '#^Method PhpParser\\BuilderFactory\:\:val\(\) has parameter \$value with no value type specified in iterable type array\.$#'
identifier: missingType.iterableValue
count: 1
path: lib/PhpParser/BuilderFactory.php
-
message: "#^Method PhpParser\\\\BuilderHelpers\\:\\:normalizeValue\\(\\) has parameter \\$value with no value type specified in iterable type array\\.$#"
message: '#^Method PhpParser\\BuilderHelpers\:\:normalizeValue\(\) has parameter \$value with no value type specified in iterable type array\.$#'
identifier: missingType.iterableValue
count: 1
path: lib/PhpParser/BuilderHelpers.php
-
message: "#^Method PhpParser\\\\ConstExprEvaluator\\:\\:evaluateArray\\(\\) return type has no value type specified in iterable type array\\.$#"
message: '#^Method PhpParser\\ConstExprEvaluator\:\:evaluateArray\(\) return type has no value type specified in iterable type array\.$#'
identifier: missingType.iterableValue
count: 1
path: lib/PhpParser/ConstExprEvaluator.php
-
message: "#^Unary operation \"~\" on mixed results in an error\\.$#"
count: 1
path: lib/PhpParser/ConstExprEvaluator.php
-
message: "#^Method PhpParser\\\\JsonDecoder\\:\\:decodeArray\\(\\) has parameter \\$array with no value type specified in iterable type array\\.$#"
message: '#^Method PhpParser\\JsonDecoder\:\:decodeArray\(\) has parameter \$array with no value type specified in iterable type array\.$#'
identifier: missingType.iterableValue
count: 1
path: lib/PhpParser/JsonDecoder.php
-
message: "#^Method PhpParser\\\\JsonDecoder\\:\\:decodeArray\\(\\) return type has no value type specified in iterable type array\\.$#"
message: '#^Method PhpParser\\JsonDecoder\:\:decodeArray\(\) return type has no value type specified in iterable type array\.$#'
identifier: missingType.iterableValue
count: 1
path: lib/PhpParser/JsonDecoder.php
-
message: "#^Method PhpParser\\\\JsonDecoder\\:\\:decodeComment\\(\\) has parameter \\$value with no value type specified in iterable type array\\.$#"
message: '#^Method PhpParser\\JsonDecoder\:\:decodeComment\(\) has parameter \$value with no value type specified in iterable type array\.$#'
identifier: missingType.iterableValue
count: 1
path: lib/PhpParser/JsonDecoder.php
-
message: "#^Method PhpParser\\\\JsonDecoder\\:\\:decodeNode\\(\\) has parameter \\$value with no value type specified in iterable type array\\.$#"
message: '#^Method PhpParser\\JsonDecoder\:\:decodeNode\(\) has parameter \$value with no value type specified in iterable type array\.$#'
identifier: missingType.iterableValue
count: 1
path: lib/PhpParser/JsonDecoder.php
-
message: "#^Call to function assert\\(\\) with false will always evaluate to false\\.$#"
message: '#^Call to function assert\(\) with false will always evaluate to false\.$#'
identifier: function.impossibleType
count: 1
path: lib/PhpParser/Lexer/Emulative.php
-
message: "#^Constant T_PRIVATE_SET not found\\.$#"
count: 2
path: lib/PhpParser/Lexer/TokenEmulator/AsymmetricVisibilityTokenEmulator.php
-
message: "#^Constant T_PROTECTED_SET not found\\.$#"
count: 2
path: lib/PhpParser/Lexer/TokenEmulator/AsymmetricVisibilityTokenEmulator.php
-
message: "#^Constant T_PUBLIC_SET not found\\.$#"
count: 2
path: lib/PhpParser/Lexer/TokenEmulator/AsymmetricVisibilityTokenEmulator.php
-
message: "#^Constant T_PROPERTY_C not found\\.$#"
message: '#^If condition is always false\.$#'
identifier: if.alwaysFalse
count: 1
path: lib/PhpParser/Lexer/TokenEmulator/PropertyTokenEmulator.php
path: lib/PhpParser/Node/Expr/ArrayItem.php
-
message: "#^Method PhpParser\\\\NodeDumper\\:\\:__construct\\(\\) has parameter \\$options with no value type specified in iterable type array\\.$#"
message: '#^If condition is always false\.$#'
identifier: if.alwaysFalse
count: 1
path: lib/PhpParser/Node/Expr/ClosureUse.php
-
message: '#^If condition is always false\.$#'
identifier: if.alwaysFalse
count: 1
path: lib/PhpParser/Node/Scalar/DNumber.php
-
message: '#^If condition is always false\.$#'
identifier: if.alwaysFalse
count: 1
path: lib/PhpParser/Node/Scalar/Encapsed.php
-
message: '#^If condition is always false\.$#'
identifier: if.alwaysFalse
count: 1
path: lib/PhpParser/Node/Scalar/EncapsedStringPart.php
-
message: '#^If condition is always false\.$#'
identifier: if.alwaysFalse
count: 1
path: lib/PhpParser/Node/Scalar/LNumber.php
-
message: '#^If condition is always false\.$#'
identifier: if.alwaysFalse
count: 1
path: lib/PhpParser/Node/Stmt/DeclareDeclare.php
-
message: '#^If condition is always false\.$#'
identifier: if.alwaysFalse
count: 1
path: lib/PhpParser/Node/Stmt/PropertyProperty.php
-
message: '#^If condition is always false\.$#'
identifier: if.alwaysFalse
count: 1
path: lib/PhpParser/Node/Stmt/StaticVar.php
-
message: '#^If condition is always false\.$#'
identifier: if.alwaysFalse
count: 1
path: lib/PhpParser/Node/Stmt/UseUse.php
-
message: '#^Method PhpParser\\NodeDumper\:\:__construct\(\) has parameter \$options with no value type specified in iterable type array\.$#'
identifier: missingType.iterableValue
count: 1
path: lib/PhpParser/NodeDumper.php
-
message: "#^Method PhpParser\\\\NodeDumper\\:\\:dump\\(\\) has parameter \\$node with no value type specified in iterable type array\\.$#"
message: '#^Method PhpParser\\NodeDumper\:\:dump\(\) has parameter \$node with no value type specified in iterable type array\.$#'
identifier: missingType.iterableValue
count: 1
path: lib/PhpParser/NodeDumper.php
-
message: "#^Method PhpParser\\\\NodeTraverser\\:\\:traverseArray\\(\\) has parameter \\$nodes with no value type specified in iterable type array\\.$#"
message: '#^Method PhpParser\\NodeTraverser\:\:traverseArray\(\) has parameter \$nodes with no value type specified in iterable type array\.$#'
identifier: missingType.iterableValue
count: 1
path: lib/PhpParser/NodeTraverser.php
-
message: "#^Method PhpParser\\\\NodeTraverser\\:\\:traverseArray\\(\\) return type has no value type specified in iterable type array\\.$#"
message: '#^Method PhpParser\\NodeTraverser\:\:traverseArray\(\) return type has no value type specified in iterable type array\.$#'
identifier: missingType.iterableValue
count: 1
path: lib/PhpParser/NodeTraverser.php
-
message: "#^Access to an undefined property PhpParser\\\\Node\\:\\:\\$attrGroups\\.$#"
message: '#^Access to an undefined property PhpParser\\Node\:\:\$attrGroups\.$#'
identifier: property.notFound
count: 1
path: lib/PhpParser/NodeVisitor/NameResolver.php
-
message: "#^Access to an undefined property PhpParser\\\\Node\\:\\:\\$name\\.$#"
message: '#^Access to an undefined property PhpParser\\Node\:\:\$name\.$#'
identifier: property.notFound
count: 1
path: lib/PhpParser/NodeVisitor/NameResolver.php
-
message: "#^Access to an undefined property PhpParser\\\\Node\\:\\:\\$namespacedName\\.$#"
message: '#^Access to an undefined property PhpParser\\Node\:\:\$namespacedName\.$#'
identifier: property.notFound
count: 1
path: lib/PhpParser/NodeVisitor/NameResolver.php
-
message: "#^Method PhpParser\\\\NodeVisitor\\\\NodeConnectingVisitor\\:\\:beforeTraverse\\(\\) should return array\\<PhpParser\\\\Node\\>\\|null but return statement is missing\\.$#"
message: '#^Method PhpParser\\NodeVisitor\\NodeConnectingVisitor\:\:beforeTraverse\(\) should return array\<PhpParser\\Node\>\|null but return statement is missing\.$#'
identifier: return.missing
count: 1
path: lib/PhpParser/NodeVisitor/NodeConnectingVisitor.php
-
message: "#^Method PhpParser\\\\NodeVisitor\\\\NodeConnectingVisitor\\:\\:enterNode\\(\\) should return array\\<PhpParser\\\\Node\\>\\|int\\|PhpParser\\\\Node\\|null but return statement is missing\\.$#"
message: '#^Method PhpParser\\NodeVisitor\\NodeConnectingVisitor\:\:enterNode\(\) should return array\<PhpParser\\Node\>\|int\|PhpParser\\Node\|null but return statement is missing\.$#'
identifier: return.missing
count: 1
path: lib/PhpParser/NodeVisitor/NodeConnectingVisitor.php
-
message: "#^Method PhpParser\\\\NodeVisitor\\\\NodeConnectingVisitor\\:\\:leaveNode\\(\\) should return array\\<PhpParser\\\\Node\\>\\|int\\|PhpParser\\\\Node\\|null but return statement is missing\\.$#"
message: '#^Method PhpParser\\NodeVisitor\\NodeConnectingVisitor\:\:leaveNode\(\) should return array\<PhpParser\\Node\>\|int\|PhpParser\\Node\|null but return statement is missing\.$#'
identifier: return.missing
count: 1
path: lib/PhpParser/NodeVisitor/NodeConnectingVisitor.php
-
message: "#^Method PhpParser\\\\NodeVisitor\\\\ParentConnectingVisitor\\:\\:beforeTraverse\\(\\) should return array\\<PhpParser\\\\Node\\>\\|null but return statement is missing\\.$#"
message: '#^Method PhpParser\\NodeVisitor\\ParentConnectingVisitor\:\:beforeTraverse\(\) should return array\<PhpParser\\Node\>\|null but return statement is missing\.$#'
identifier: return.missing
count: 1
path: lib/PhpParser/NodeVisitor/ParentConnectingVisitor.php
-
message: "#^Method PhpParser\\\\NodeVisitor\\\\ParentConnectingVisitor\\:\\:enterNode\\(\\) should return array\\<PhpParser\\\\Node\\>\\|int\\|PhpParser\\\\Node\\|null but return statement is missing\\.$#"
message: '#^Method PhpParser\\NodeVisitor\\ParentConnectingVisitor\:\:enterNode\(\) should return array\<PhpParser\\Node\>\|int\|PhpParser\\Node\|null but return statement is missing\.$#'
identifier: return.missing
count: 1
path: lib/PhpParser/NodeVisitor/ParentConnectingVisitor.php
-
message: "#^Method PhpParser\\\\NodeVisitor\\\\ParentConnectingVisitor\\:\\:leaveNode\\(\\) should return array\\<PhpParser\\\\Node\\>\\|int\\|PhpParser\\\\Node\\|null but return statement is missing\\.$#"
message: '#^Method PhpParser\\NodeVisitor\\ParentConnectingVisitor\:\:leaveNode\(\) should return array\<PhpParser\\Node\>\|int\|PhpParser\\Node\|null but return statement is missing\.$#'
identifier: return.missing
count: 1
path: lib/PhpParser/NodeVisitor/ParentConnectingVisitor.php
-
message: "#^Access to undefined constant static\\(PhpParser\\\\ParserAbstract\\)\\:\\:T_ECHO\\.$#"
message: '#^Access to undefined constant static\(PhpParser\\ParserAbstract\)\:\:T_ECHO\.$#'
identifier: classConstant.notFound
count: 1
path: lib/PhpParser/ParserAbstract.php
-
message: "#^Unary operation \"\\+\" on string results in an error\\.$#"
message: '#^Unary operation "\+" on string results in an error\.$#'
identifier: unaryOp.invalid
count: 1
path: lib/PhpParser/ParserAbstract.php
-
message: "#^Variable \\$action might not be defined\\.$#"
message: '#^Variable \$action might not be defined\.$#'
identifier: variable.undefined
count: 1
path: lib/PhpParser/ParserAbstract.php

View File

@ -24,7 +24,7 @@ class CodeTestParser {
// multiple sections possible with always two forming a pair
$chunks = array_chunk($parts, $chunksPerTest);
$tests = [];
foreach ($chunks as $i => $chunk) {
foreach ($chunks as $chunk) {
$lastPart = array_pop($chunk);
list($lastPart, $mode) = $this->extractMode($lastPart);
$tests[] = [$mode, array_merge($chunk, [$lastPart])];
@ -61,7 +61,7 @@ class CodeTestParser {
return [$expected, null];
}
$expected = (string) substr($expected, $firstNewLine + 1);
$expected = substr($expected, $firstNewLine + 1);
return [$expected, substr($firstLine, 2)];
}
}

View File

@ -47,4 +47,18 @@ class ParamTest extends \PHPUnit\Framework\TestCase {
$node->flags = Modifiers::PUBLIC_SET;
$this->assertTrue($node->isPublicSet());
}
public function testPromotedPropertyWithoutVisibilityModifier(): void {
$node = new Param(new Variable('foo'));
$get = new PropertyHook('get', null);
$node->hooks[] = $get;
$this->assertTrue($node->isPromoted());
$this->assertTrue($node->isPublic());
}
public function testNonPromotedPropertyIsNotPublic(): void {
$node = new Param(new Variable('foo'));
$this->assertFalse($node->isPublic());
}
}

View File

@ -0,0 +1,89 @@
<?php declare(strict_types=1);
namespace PhpParser\Node;
use PhpParser\Modifiers;
use PhpParser\Node\Expr\Assign;
use PhpParser\Node\Expr\PropertyFetch;
use PhpParser\Node\Expr\Variable;
use PhpParser\Node\Scalar\Int_;
use PhpParser\Node\Stmt\Expression;
use PhpParser\Node\Stmt\Return_;
use PhpParser\ParserFactory;
use PhpParser\PrettyPrinter\Standard;
class PropertyHookTest extends \PHPUnit\Framework\TestCase {
/**
* @dataProvider provideModifiers
*/
public function testModifiers($modifier): void {
$node = new PropertyHook(
'get',
null,
[
'flags' => constant(Modifiers::class . '::' . strtoupper($modifier)),
]
);
$this->assertTrue($node->{'is' . $modifier}());
}
public function testNoModifiers(): void {
$node = new PropertyHook('get', null);
$this->assertFalse($node->isFinal());
}
public static function provideModifiers() {
return [
['final'],
];
}
public function testGetStmts(): void {
$expr = new Variable('test');
$get = new PropertyHook('get', $expr);
$this->assertEquals([new Return_($expr)], $get->getStmts());
$set = new PropertyHook('set', $expr, [], ['propertyName' => 'abc']);
$this->assertEquals([
new Expression(new Assign(new PropertyFetch(new Variable('this'), 'abc'), $expr))
], $set->getStmts());
}
public function testGetStmtsSetHookFromParser(): void {
$parser = (new ParserFactory())->createForNewestSupportedVersion();
$prettyPrinter = new Standard();
$stmts = $parser->parse(<<<'CODE'
<?php
class Test {
public $prop1 { set => 123; }
public function __construct(public $prop2 { set => 456; }) {}
}
CODE);
$hook1 = $stmts[0]->stmts[0]->hooks[0];
$this->assertEquals('$this->prop1 = 123;', $prettyPrinter->prettyPrint($hook1->getStmts()));
$hook2 = $stmts[0]->stmts[1]->params[0]->hooks[0];
$this->assertEquals('$this->prop2 = 456;', $prettyPrinter->prettyPrint($hook2->getStmts()));
}
public function testGetStmtsUnknownHook(): void {
$expr = new Variable('test');
$hook = new PropertyHook('foobar', $expr);
$this->expectException(\LogicException::class);
$this->expectExceptionMessage('Unknown property hook "foobar"');
$hook->getStmts();
}
public function testGetStmtsSetHookWithoutPropertyName(): void {
$expr = new Variable('test');
$set = new PropertyHook('set', $expr);
$this->expectException(\LogicException::class);
$this->expectExceptionMessage('Can only use getStmts() on a "set" hook if the "propertyName" attribute is set');
$set->getStmts();
}
}

View File

@ -15,11 +15,11 @@ class DNumberTest extends \PHPUnit\Framework\TestCase {
$this->assertInstanceOf(Echo_::class, $echo);
/** @var Echo_ $echo */
$lLumber = $echo->exprs[0];
$this->assertInstanceOf(Float_::class, $lLumber);
$dnumber = $echo->exprs[0];
$this->assertInstanceOf(Float_::class, $dnumber);
/** @var Float_ $dnumber */
$this->assertSame(1234.56, $lLumber->value);
$this->assertSame('1_234.56', $lLumber->getAttribute('rawValue'));
$this->assertSame(1234.56, $dnumber->value);
$this->assertSame('1_234.56', $dnumber->getAttribute('rawValue'));
}
}

View File

@ -14,11 +14,11 @@ class NumberTest extends \PHPUnit\Framework\TestCase {
$this->assertInstanceOf(Echo_::class, $echo);
/** @var Echo_ $echo */
$lLumber = $echo->exprs[0];
$this->assertInstanceOf(Int_::class, $lLumber);
$lnumber = $echo->exprs[0];
$this->assertInstanceOf(Int_::class, $lnumber);
/** @var Int_ $lnumber */
$this->assertSame(1234, $lLumber->value);
$this->assertSame('1_234', $lLumber->getAttribute('rawValue'));
$this->assertSame(1234, $lnumber->value);
$this->assertSame('1_234', $lnumber->getAttribute('rawValue'));
}
}

View File

@ -57,4 +57,14 @@ class PropertyTest extends \PHPUnit\Framework\TestCase {
$node = new Property(Modifiers::PUBLIC_SET, []);
$this->assertTrue($node->isPublicSet());
}
public function testIsFinal() {
$node = new Property(Modifiers::FINAL, []);
$this->assertTrue($node->isFinal());
}
public function testIsAbstract() {
$node = new Property(Modifiers::ABSTRACT, []);
$this->assertTrue($node->isAbstract());
}
}

View File

@ -201,6 +201,18 @@ class A extends B implements C, D {
public const X A = X::Bar;
public const X\Foo B = X\Foo::Bar;
public const \X\Foo C = \X\Foo::Bar;
public Foo $foo {
#[X]
set(#[X] Bar $v) {}
}
public function __construct(
public Foo $bar {
#[X]
set(#[X] Bar $v) {}
}
) {}
}
#[X]
@ -269,6 +281,18 @@ class A extends \NS\B implements \NS\C, \NS\D
public const \NS\X A = \NS\X::Bar;
public const \NS\X\Foo B = \NS\X\Foo::Bar;
public const \X\Foo C = \X\Foo::Bar;
public \NS\Foo $foo {
#[\NS\X]
set(#[\NS\X] \NS\Bar $v) {
}
}
public function __construct(public \NS\Foo $bar {
#[\NS\X]
set(#[\NS\X] \NS\Bar $v) {
}
})
{
}
}
#[\NS\X]
interface A extends \NS\C, \NS\D
@ -543,7 +567,7 @@ EOC;
}
private function parseAndResolve(string $code): array {
$parser = new PhpParser\Parser\Php7(new PhpParser\Lexer\Emulative());
$parser = new PhpParser\Parser\Php8(new PhpParser\Lexer\Emulative());
$traverser = new PhpParser\NodeTraverser();
$traverser->addVisitor(new NameResolver());

View File

@ -30,4 +30,28 @@ final class NodeConnectingVisitorTest extends \PHPUnit\Framework\TestCase {
$this->assertSame(Else_::class, get_class($node->getAttribute('next')));
}
public function testWeakReferences(): void {
$ast = (new ParserFactory())->createForNewestSupportedVersion()->parse(
'<?php if (true) {} else {}'
);
$traverser = new NodeTraverser();
$traverser->addVisitor(new NodeConnectingVisitor(true));
$ast = $traverser->traverse($ast);
$node = (new NodeFinder())->findFirstInstanceof($ast, Else_::class);
$this->assertInstanceOf(\WeakReference::class, $node->getAttribute('weak_parent'));
$this->assertSame(If_::class, get_class($node->getAttribute('weak_parent')->get()));
$this->assertInstanceOf(\WeakReference::class, $node->getAttribute('weak_previous'));
$this->assertSame(ConstFetch::class, get_class($node->getAttribute('weak_previous')->get()));
$node = (new NodeFinder())->findFirstInstanceof($ast, ConstFetch::class);
$this->assertInstanceOf(\WeakReference::class, $node->getAttribute('weak_next'));
$this->assertSame(Else_::class, get_class($node->getAttribute('weak_next')->get()));
}
}

View File

@ -23,4 +23,22 @@ final class ParentConnectingVisitorTest extends \PHPUnit\Framework\TestCase {
$this->assertSame('C', $node->getAttribute('parent')->name->toString());
}
public function testWeakReferences(): void {
$ast = (new ParserFactory())->createForNewestSupportedVersion()->parse(
'<?php class C { public function m() {} }'
);
$traverser = new NodeTraverser();
$traverser->addVisitor(new ParentConnectingVisitor(true));
$ast = $traverser->traverse($ast);
$node = (new NodeFinder())->findFirstInstanceof($ast, ClassMethod::class);
$weakReference = $node->getAttribute('weak_parent');
$this->assertInstanceOf(\WeakReference::class, $weakReference);
$this->assertSame('C', $weakReference->get()->name->toString());
}
}

View File

@ -17,11 +17,13 @@ class PrettyPrinterTest extends CodeTestAbstract {
private function createParserAndPrinter(array $options): array {
$parserVersion = $options['parserVersion'] ?? $options['version'] ?? null;
$printerVersion = $options['version'] ?? null;
$indent = isset($options['indent']) ? json_decode($options['indent']) : null;
$factory = new ParserFactory();
$parser = $factory->createForVersion($parserVersion !== null
? PhpVersion::fromString($parserVersion) : PhpVersion::getNewestSupported());
$prettyPrinter = new Standard([
'phpVersion' => $printerVersion !== null ? PhpVersion::fromString($printerVersion) : null
'phpVersion' => $printerVersion !== null ? PhpVersion::fromString($printerVersion) : null,
'indent' => $indent,
]);
return [$parser, $prettyPrinter];
}
@ -297,4 +299,10 @@ CODE
$this->expectExceptionMessage('Option "newline" must be one of "\n" or "\r\n"');
new PrettyPrinter\Standard(['newline' => 'foo']);
}
public function testInvalidIndent(): void {
$this->expectException(\LogicException::class);
$this->expectExceptionMessage('Option "indent" must either be all spaces or a single tab');
new PrettyPrinter\Standard(['indent' => "\t "]);
}
}

View File

@ -0,0 +1,9 @@
Group use should include trailing semicolon
-----
<?php
use A\{B, C};
-----
$stmts[0] = new Stmt\Use_([new Node\UseItem(new Node\Name('A\B'))]);
-----
<?php
use A\B;

View File

@ -0,0 +1,70 @@
Indentation
-----
<?php
$x;
-----
$stmts[0] = new Stmt\If_(new Expr\Variable('a'), ['stmts' => $stmts]);
-----
!!indent=" "
<?php
if ($a) {
$x;
}
-----
<?php
$x;
-----
$stmts[0] = new Stmt\If_(new Expr\Variable('a'), ['stmts' => $stmts]);
-----
!!indent="\t"
<?php
if ($a) {
@@{"\t"}@@$x;
}
-----
<?php
if ($a) {
@@{"\t"}@@$x;
}
-----
$stmts[0]->stmts[] = new Stmt\Expression(new Expr\Variable('y'));
-----
!!indent="\t"
<?php
if ($a) {
@@{"\t"}@@$x;
@@{"\t"}@@$y;
}
-----
<?php
array_merge(
[
$x,
$y,
]
);
-----
$call = $stmts[0]->expr;
$stmts[0]->expr = new Expr\StaticCall(new Node\Name\FullyQualified('Compat'), $call->name, $call->args);
-----
<?php
\Compat::array_merge([
$x,
$y,
]);
-----
<?php
if ($a) {
if (
$b
) {}
}
-----
$stmts[0] = new Stmt\While_($stmts[0]->cond, $stmts[0]->stmts);
-----
<?php
while ($a) {
if (
$b
) {}
}

View File

@ -523,3 +523,116 @@ array(
)
)
)
-----
<?php
class Test
{
public $foo, $bar { get { return 42; } }
}
-----
Cannot use hooks when declaring multiple properties from 5:23 to 5:23
array(
0: Stmt_Class(
attrGroups: array(
)
flags: 0
name: Identifier(
name: Test
)
extends: null
implements: array(
)
stmts: array(
0: Stmt_Property(
attrGroups: array(
)
flags: PUBLIC (1)
type: null
props: array(
0: PropertyItem(
name: VarLikeIdentifier(
name: foo
)
default: null
)
1: PropertyItem(
name: VarLikeIdentifier(
name: bar
)
default: null
)
)
hooks: array(
0: PropertyHook(
attrGroups: array(
)
flags: 0
byRef: false
name: Identifier(
name: get
)
params: array(
)
body: array(
0: Stmt_Return(
expr: Scalar_Int(
value: 42
)
)
)
)
)
)
)
)
)
-----
<?php
class Test
{
public $foo, $bar { }
}
-----
Cannot use hooks when declaring multiple properties from 5:23 to 5:23
Property hook list cannot be empty from 5:23 to 5:23
array(
0: Stmt_Class(
attrGroups: array(
)
flags: 0
name: Identifier(
name: Test
)
extends: null
implements: array(
)
stmts: array(
0: Stmt_Property(
attrGroups: array(
)
flags: PUBLIC (1)
type: null
props: array(
0: PropertyItem(
name: VarLikeIdentifier(
name: foo
)
default: null
)
1: PropertyItem(
name: VarLikeIdentifier(
name: bar
)
default: null
)
)
hooks: array(
)
)
)
)
)

View File

@ -0,0 +1,90 @@
Declaring exit and die function stubs
-----
<?php
function exit(string|int $status = 0): never {}
function die(string|int $status = 0): never {}
-----
array(
0: Stmt_Function(
attrGroups: array(
)
byRef: false
name: Identifier(
name: exit
)
params: array(
0: Param(
attrGroups: array(
)
flags: 0
type: UnionType(
types: array(
0: Identifier(
name: string
)
1: Identifier(
name: int
)
)
)
byRef: false
variadic: false
var: Expr_Variable(
name: status
)
default: Scalar_Int(
value: 0
)
hooks: array(
)
)
)
returnType: Identifier(
name: never
)
stmts: array(
)
)
1: Stmt_Function(
attrGroups: array(
)
byRef: false
name: Identifier(
name: die
)
params: array(
0: Param(
attrGroups: array(
)
flags: 0
type: UnionType(
types: array(
0: Identifier(
name: string
)
1: Identifier(
name: int
)
)
)
byRef: false
variadic: false
var: Expr_Variable(
name: status
)
default: Scalar_Int(
value: 0
)
hooks: array(
)
)
)
returnType: Identifier(
name: never
)
stmts: array(
)
)
)

View File

@ -5,7 +5,7 @@ use Foo\Bar\{Baz};
-----
!!positions
array(
0: Stmt_GroupUse[2:1 - 2:17](
0: Stmt_GroupUse[2:1 - 2:18](
type: TYPE_UNKNOWN (0)
prefix: Name[2:5 - 2:11](
name: Foo\Bar

View File

@ -0,0 +1,82 @@
Indentation
-----
<?php
class Test {
/**
* Comment
*/
public function foo() {
if (1) {
echo $bar;
}
}
}
-----
!!indent=" "
class Test
{
/**
* Comment
*/
public function foo()
{
if (1) {
echo $bar;
}
}
}
-----
<?php
class Test {
/**
* Comment
*/
public function foo() {
if (1) {
echo $bar;
}
}
}
-----
!!indent=" "
class Test
{
/**
* Comment
*/
public function foo()
{
if (1) {
echo $bar;
}
}
}
-----
<?php
class Test {
/**
* Comment
*/
public function foo() {
if (1) {
echo $bar;
}
}
}
-----
!!indent="\t"
class Test
{
@@{"\t"}@@/**
@@{"\t"}@@ * Comment
@@{"\t"}@@ */
@@{"\t"}@@public function foo()
@@{"\t"}@@{
@@{"\t\t"}@@if (1) {
@@{"\t\t\t"}@@echo $bar;
@@{"\t\t"}@@}
@@{"\t"}@@}
}

View File

@ -1,6 +1,6 @@
{
"require": {
"friendsofphp/php-cs-fixer": "^3.10",
"phpstan/phpstan": "^1.8"
"phpstan/phpstan": "^2.0"
}
}

View File

@ -8,7 +8,7 @@ require $testDir . '/PhpParser/CodeParsingTest.php';
$inputDirs = [$testDir . '/code/parser', $testDir . '/code/prettyPrinter'];
if ($argc < 2) {
echo "Usage: php generateCorpus.php dir/";
echo "Usage: php generateCorpus.php dir/\n";
exit(1);
}

View File

@ -104,7 +104,7 @@ $fuzzer->setTarget(function(string $input) use($lexer, $parser, $prettyPrinter,
$stmts = $parser->parse($input);
$printed = $prettyPrinter->prettyPrintFile($stmts);
$visitor->setTokens($lexer->getTokens());
$visitor->setTokens($parser->getTokens());
$stmts = $traverser->traverse($stmts);
if ($visitor->hasProblematicConstruct) {
return;
@ -116,7 +116,7 @@ $fuzzer->setTarget(function(string $input) use($lexer, $parser, $prettyPrinter,
throw new Error("Failed to parse pretty printer output");
}
$visitor->setTokens($lexer->getTokens());
$visitor->setTokens($parser->getTokens());
$printedStmts = $traverser->traverse($printedStmts);
$same = $nodeDumper->dump($stmts) == $nodeDumper->dump($printedStmts);
if (!$same && !preg_match('/<\?php<\?php/i', $input)) {