mirror of
https://github.com/nikic/PHP-Parser.git
synced 2025-04-20 22:12:25 +02:00
Compare commits
32 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
c9d0b6c9f2 | ||
|
14f9c9df7f | ||
|
fa02db3f30 | ||
|
d4fce83c2c | ||
|
7d3039c378 | ||
|
447a020a1f | ||
|
45f70ed80a | ||
|
74a361814d | ||
|
6478c5ac53 | ||
|
8bb415902e | ||
|
48fd76e542 | ||
|
d20a197ca7 | ||
|
62dee28027 | ||
|
b396e9e0d6 | ||
|
f212bb7afb | ||
|
f43324a074 | ||
|
73b160f8c4 | ||
|
4f9dc8b0f5 | ||
|
469377f4a8 | ||
|
74d3f7fc24 | ||
|
25828ea952 | ||
|
05ed79595b | ||
|
954f7a411f | ||
|
8eea230464 | ||
|
9c7a3f8d8f | ||
|
26573ea64f | ||
|
3abf7425cd | ||
|
961f158f6d | ||
|
e50c67b7a9 | ||
|
26a0197186 | ||
|
aedfcc23cd | ||
|
8d09ba87f4 |
4
.github/workflows/main.yml
vendored
4
.github/workflows/main.yml
vendored
@ -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"
|
||||
|
43
CHANGELOG.md
43
CHANGELOG.md
@ -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)
|
||||
--------------------------
|
||||
|
||||
|
7
Makefile
7
Makefile
@ -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
|
@ -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')`.
|
@ -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.
|
||||
|
||||
|
@ -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
|
||||
;
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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;
|
||||
|
||||
/**
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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_ {
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
}
|
||||
}
|
||||
|
@ -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_ {
|
||||
}
|
||||
}
|
||||
|
@ -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 = [];
|
||||
|
@ -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 {
|
||||
}
|
||||
}
|
||||
|
@ -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.
|
||||
*/
|
||||
|
@ -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 {
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
}
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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');
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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
@ -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) {
|
||||
|
@ -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();
|
||||
|
@ -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
|
||||
|
@ -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)];
|
||||
}
|
||||
}
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
89
test/PhpParser/Node/PropertyHookTest.php
Normal file
89
test/PhpParser/Node/PropertyHookTest.php
Normal 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();
|
||||
}
|
||||
}
|
@ -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'));
|
||||
}
|
||||
}
|
||||
|
@ -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'));
|
||||
}
|
||||
}
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
@ -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());
|
||||
|
||||
|
@ -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()));
|
||||
}
|
||||
}
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
@ -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 "]);
|
||||
}
|
||||
}
|
||||
|
9
test/code/formatPreservation/group_use.test
Normal file
9
test/code/formatPreservation/group_use.test
Normal 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;
|
70
test/code/formatPreservation/indent.test
Normal file
70
test/code/formatPreservation/indent.test
Normal 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
|
||||
) {}
|
||||
}
|
@ -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(
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
90
test/code/parser/stmt/function/exit_die_function.test
Normal file
90
test/code/parser/stmt/function/exit_die_function.test
Normal 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(
|
||||
)
|
||||
)
|
||||
)
|
@ -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
|
||||
|
82
test/code/prettyPrinter/indent.test
Normal file
82
test/code/prettyPrinter/indent.test
Normal 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"}@@}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"require": {
|
||||
"friendsofphp/php-cs-fixer": "^3.10",
|
||||
"phpstan/phpstan": "^1.8"
|
||||
"phpstan/phpstan": "^2.0"
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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)) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user