Consider side effects when inserting statements before other statements
This commit is contained in:
parent
af55021140
commit
1265dd87ff
|
@ -10,7 +10,8 @@
|
|||
"phpunit/phpunit": "^7 | ^8 | ^9",
|
||||
"amphp/php-cs-fixer-config": "dev-master",
|
||||
"composer/composer": "^1|^2",
|
||||
"haydenpierce/class-finder": "^0.4.2"
|
||||
"haydenpierce/class-finder": "^0.4.2",
|
||||
"vimeo/psalm": "dev-master"
|
||||
},
|
||||
"license": "MIT",
|
||||
"authors": [{
|
||||
|
|
186
src/Context.php
186
src/Context.php
|
@ -17,6 +17,7 @@ use PhpParser\Node\Expr\Cast\Bool_;
|
|||
use PhpParser\Node\Expr\Closure;
|
||||
use PhpParser\Node\Expr\FuncCall;
|
||||
use PhpParser\Node\Expr\MethodCall;
|
||||
use PhpParser\Node\Expr\New_;
|
||||
use PhpParser\Node\Expr\StaticCall;
|
||||
use PhpParser\Node\Expr\Ternary;
|
||||
use PhpParser\Node\Expr\Variable;
|
||||
|
@ -50,7 +51,9 @@ class Context
|
|||
*/
|
||||
public function __construct()
|
||||
{
|
||||
/** @var SplStack<Node> */
|
||||
$this->parents = new SplStack;
|
||||
/** @var SplStack<VariableContext> */
|
||||
$this->variables = new SplStack;
|
||||
}
|
||||
/**
|
||||
|
@ -146,13 +149,13 @@ class Context
|
|||
/**
|
||||
* Insert nodes before node.
|
||||
*
|
||||
* @param Node $node Node before which to insert nodes
|
||||
* @param Node ...$nodes Nodes to insert
|
||||
* @param Node $node Node before which to insert nodes
|
||||
* @param Node ...$insert Nodes to insert
|
||||
* @return void
|
||||
*/
|
||||
public function insertBefore(Node $node, Node ...$nodes): void
|
||||
public function insertBefore(Node $node, Node ...$insert): void
|
||||
{
|
||||
if (empty($nodes)) {
|
||||
if (empty($insert)) {
|
||||
return;
|
||||
}
|
||||
$found = false;
|
||||
|
@ -165,106 +168,105 @@ class Context
|
|||
if (!$found) {
|
||||
throw new \RuntimeException('Node is not a part of the current AST stack!');
|
||||
}
|
||||
$this->insertBeforeParent($parent, $nodes);
|
||||
}
|
||||
/**
|
||||
* Insert nodes before node.
|
||||
*
|
||||
* @param Node $node Node before which to insert nodes
|
||||
* @param Node[] $nodes Nodes to insert
|
||||
* @return void
|
||||
*/
|
||||
private function insertBeforeParent(Node $parent, array $nodes): void
|
||||
{
|
||||
$subNode = $parent->getAttribute('currentNode');
|
||||
if ($subNode === 'stmts') {
|
||||
$subNodeIndex = $parent->getAttribute('currentNodeIndex');
|
||||
\array_splice($parent->{$subNode}, $subNodeIndex, 0, $nodes);
|
||||
|
||||
/** @var string */
|
||||
$nodeKey = $parent->getAttribute('currentNode');
|
||||
if ($nodeKey === 'stmts') {
|
||||
/** @var int */
|
||||
$nodeKeyIndex = $parent->getAttribute('currentNodeIndex');
|
||||
\array_splice($parent->{$nodeKey}, $nodeKeyIndex, 0, $insert);
|
||||
$skips = $parent->getAttribute('skipNodes', []);
|
||||
$skips []= $subNodeIndex+\count($nodes);
|
||||
$skips []= $nodeKeyIndex+\count($insert);
|
||||
$parent->setAttribute('skipNodes', $skips);
|
||||
$parent->setAttribute('currentNodeIndex', $subNodeIndex - 1);
|
||||
} else { // Cannot insert, is not in a statement
|
||||
$curNode = &$parent->{$subNode};
|
||||
if ($curNode instanceof BooleanOr && $subNode === 'right') {
|
||||
$result = $this->getVariable();
|
||||
$nodes = new If_(
|
||||
$curNode->left,
|
||||
[
|
||||
'stmts' => [
|
||||
new Assign($result, BuilderHelpers::normalizeValue(true))
|
||||
],
|
||||
'else' => [
|
||||
...$nodes,
|
||||
new Assign($result, new Bool_($curNode->right))
|
||||
]
|
||||
$parent->setAttribute('currentNodeIndex', $nodeKeyIndex - 1);
|
||||
return; // Done, inserted!
|
||||
}
|
||||
|
||||
// Cannot insert, parent is not a statement
|
||||
$node = &$parent->{$nodeKey};
|
||||
// If we insert before a conditional branch of a conditional expression,
|
||||
// make sure the conditional branch has no side effects;
|
||||
// if it does, turn the entire conditional expression into an if, and bubble it up
|
||||
//
|
||||
// Unless we want to go crazy, do not consider side effect evaluation order for stuff like function call arguments, maths and so on.
|
||||
//
|
||||
if ($node instanceof BooleanOr && $nodeKey === 'right' && Tools::hasSideEffects($node->right)) {
|
||||
$result = $this->getVariable();
|
||||
$insert = new If_(
|
||||
$node->left,
|
||||
[
|
||||
'stmts' => [
|
||||
new Assign($result, BuilderHelpers::normalizeValue(true))
|
||||
],
|
||||
'else' => [
|
||||
...$insert,
|
||||
new Assign($result, new Bool_($node->right))
|
||||
]
|
||||
);
|
||||
$curNode = $result;
|
||||
} elseif ($curNode instanceof BooleanAnd && $subNode === 'right') {
|
||||
$result = $this->getVariable();
|
||||
$nodes = new If_(
|
||||
$curNode->left,
|
||||
[
|
||||
'stmts' => [
|
||||
...$nodes,
|
||||
new Assign($result, new Bool_($curNode->right))
|
||||
],
|
||||
'else' => [
|
||||
new Assign($result, BuilderHelpers::normalizeValue(false))
|
||||
]
|
||||
]
|
||||
);
|
||||
$node = $result;
|
||||
} elseif ($node instanceof BooleanAnd && $nodeKey === 'right' && Tools::hasSideEffects($node->right)) {
|
||||
$result = $this->getVariable();
|
||||
$insert = new If_(
|
||||
$node->left,
|
||||
[
|
||||
'stmts' => [
|
||||
...$insert,
|
||||
new Assign($result, new Bool_($node->right))
|
||||
],
|
||||
'else' => [
|
||||
new Assign($result, BuilderHelpers::normalizeValue(false))
|
||||
]
|
||||
);
|
||||
$curNode = $result;
|
||||
} elseif ($curNode instanceof Ternary && $subNode !== 'cond') {
|
||||
$result = $this->getVariable();
|
||||
if (!$curNode->if) { // ?:
|
||||
$nodes = new If_(
|
||||
new BooleanNot(
|
||||
new Assign($result, $curNode->cond)
|
||||
),
|
||||
[
|
||||
'stmts' => [
|
||||
...$nodes,
|
||||
new Assign($result, $curNode->else)
|
||||
]
|
||||
]
|
||||
);
|
||||
} else {
|
||||
$nodes = new If_(
|
||||
$curNode->cond,
|
||||
[
|
||||
'stmts' => [
|
||||
...$subNode === 'if' ? $nodes : [],
|
||||
new Assign($result, $curNode->if)
|
||||
],
|
||||
'else' => [
|
||||
...$subNode === 'else' ? $nodes : [],
|
||||
new Assign($result, $curNode->else)
|
||||
]
|
||||
]
|
||||
);
|
||||
}
|
||||
$curNode = $result;
|
||||
} elseif ($subNode instanceof Coalesce && $subNode === 'right') {
|
||||
$result = $this->getVariable();
|
||||
$nodes = new If_(
|
||||
Plugin::call(
|
||||
'is_null',
|
||||
new Assign($result, $curNode->left)
|
||||
]
|
||||
);
|
||||
$node = $result;
|
||||
} elseif ($node instanceof Ternary && $nodeKey !== 'cond' && (Tools::hasSideEffects($node->if) || Tools::hasSideEffects($node->else))) {
|
||||
$result = $this->getVariable();
|
||||
if (!$node->if) { // ?:
|
||||
$insert = new If_(
|
||||
new BooleanNot(
|
||||
new Assign($result, $node->cond)
|
||||
),
|
||||
[
|
||||
'stmts' => [
|
||||
...$nodes,
|
||||
new Assign($result, $curNode->right)
|
||||
...$insert,
|
||||
new Assign($result, $node->else)
|
||||
]
|
||||
]
|
||||
);
|
||||
} else {
|
||||
$insert = new If_(
|
||||
$node->cond,
|
||||
[
|
||||
'stmts' => [
|
||||
...$nodeKey === 'if' ? $insert : [],
|
||||
new Assign($result, $node->if)
|
||||
],
|
||||
'else' => [
|
||||
...$nodeKey === 'else' ? $insert : [],
|
||||
new Assign($result, $node->else)
|
||||
]
|
||||
]
|
||||
);
|
||||
$curNode = $result;
|
||||
} elseif ($subNode instanceof FuncCall) {
|
||||
}
|
||||
$this->insertBefore($parent, $nodes);
|
||||
$node = $result;
|
||||
} elseif ($node instanceof Coalesce && $nodeKey === 'right' && Tools::hasSideEffects($node->right)) {
|
||||
$result = $this->getVariable();
|
||||
$insert = new If_(
|
||||
Plugin::call(
|
||||
'is_null',
|
||||
new Assign($result, $node->left)
|
||||
),
|
||||
[
|
||||
'stmts' => [
|
||||
...$insert,
|
||||
new Assign($result, $node->right)
|
||||
]
|
||||
]
|
||||
);
|
||||
$node = $result;
|
||||
}
|
||||
$this->insertBefore($parent, $insert);
|
||||
}
|
||||
/**
|
||||
* Insert nodes after node.
|
||||
|
|
128
src/Plugin.php
128
src/Plugin.php
|
@ -3,18 +3,10 @@
|
|||
namespace Phabel;
|
||||
|
||||
use Phabel\PluginGraph\PackageContext;
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Arg;
|
||||
use PhpParser\Node\Expr;
|
||||
use PhpParser\Node\Expr\Assign;
|
||||
use PhpParser\Node\Expr\FuncCall;
|
||||
use PhpParser\Node\Expr\MethodCall;
|
||||
use PhpParser\Node\Expr\StaticCall;
|
||||
use PhpParser\Node\Expr\Variable;
|
||||
use PhpParser\Node\Name;
|
||||
use PhpParser\Node\Stmt\Expression;
|
||||
use PhpParser\ParserFactory;
|
||||
use ReflectionClass;
|
||||
|
||||
/**
|
||||
* Plugin.
|
||||
|
@ -22,7 +14,7 @@ use ReflectionClass;
|
|||
* @author Daniil Gentili <daniil@daniil.it>
|
||||
* @license MIT
|
||||
*/
|
||||
abstract class Plugin implements PluginInterface
|
||||
abstract class Plugin extends Tools implements PluginInterface
|
||||
{
|
||||
/**
|
||||
* Configuration array.
|
||||
|
@ -84,82 +76,6 @@ abstract class Plugin implements PluginInterface
|
|||
{
|
||||
return true;
|
||||
}
|
||||
/**
|
||||
* Replace node of one type with another.
|
||||
*
|
||||
* @param Node $node Original node
|
||||
* @param string $class Class of new node
|
||||
* @param array $propertyMap Property map between old and new objects
|
||||
*
|
||||
* @psalm-param class-string<Node> $class Class of new node
|
||||
* @psalm-param array<string, string> $propertyMap Property map between old and new objects
|
||||
*
|
||||
* @return Node
|
||||
*/
|
||||
public static function replaceType(Node $node, string $class, array $propertyMap = []): Node
|
||||
{
|
||||
if ($propertyMap) {
|
||||
/** @var Node */
|
||||
$nodeNew = (new ReflectionClass($class))->newInstanceWithoutConstructor();
|
||||
foreach ($propertyMap as $old => $new) {
|
||||
$nodeNew->{$new} = $node->{$old};
|
||||
}
|
||||
$nodeNew->setAttributes($node->getAttributes());
|
||||
return $nodeNew;
|
||||
}
|
||||
return new $class(
|
||||
...\array_map(fn (string $name) => $node->{$name}, $node->getSubNodeNames()),
|
||||
$node->getAttributes()
|
||||
);
|
||||
}
|
||||
/**
|
||||
* Replace type in-place.
|
||||
*
|
||||
* @param Node &$node Original node
|
||||
* @param string $class Class of new node
|
||||
* @param array $propertyMap Property map between old and new objects
|
||||
*
|
||||
* @psalm-param class-string<Node> $class Class of new node
|
||||
* @psalm-param array<string, string> $propertyMap Property map between old and new objects
|
||||
*
|
||||
* @param-out Node &$node
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function replaceTypeInPlace(Node &$node, string $class, array $propertyMap = []): void
|
||||
{
|
||||
$node = self::replaceType($node, $class, $propertyMap);
|
||||
}
|
||||
/**
|
||||
* Create variable assignment.
|
||||
*
|
||||
* @param Variable $name Variable
|
||||
* @param Expr $expression Expression
|
||||
*
|
||||
* @return Expression
|
||||
*/
|
||||
public static function assign(Variable $name, Expr $expression): Expression
|
||||
{
|
||||
return new Expression(
|
||||
new Assign(
|
||||
$name,
|
||||
$expression
|
||||
)
|
||||
);
|
||||
}
|
||||
/**
|
||||
* Call function.
|
||||
*
|
||||
* @param class-string|array{0: class-string, 1: string}|callable-string $name Function name
|
||||
* @param Expr|Arg ...$parameters Parameters
|
||||
*
|
||||
* @return FuncCall|StaticCall
|
||||
*/
|
||||
public static function call($name, ...$parameters)
|
||||
{
|
||||
$parameters = \array_map(fn ($data) => $data instanceof Arg ? $data : new Arg($data), $parameters);
|
||||
return \is_array($name) ? new StaticCall(new Name($name[0]), $name[1], $parameters) : new FuncCall(new Name($name), $parameters);
|
||||
}
|
||||
/**
|
||||
* Call polyfill function from current plugin.
|
||||
*
|
||||
|
@ -172,48 +88,6 @@ abstract class Plugin implements PluginInterface
|
|||
{
|
||||
return self::call([static::class, $name], ...$parameters);
|
||||
}
|
||||
/**
|
||||
* Call method of object.
|
||||
*
|
||||
* @param Expr $name Object name
|
||||
* @param string $method Method
|
||||
* @param Expr|Arg ...$parameters Parameters
|
||||
*
|
||||
* @return MethodCall
|
||||
*/
|
||||
public static function callMethod(Expr $name, string $method, ...$parameters): MethodCall
|
||||
{
|
||||
$parameters = \array_map(fn ($data) => $data instanceof Arg ? $data : new Arg($data), $parameters);
|
||||
return new MethodCall($name, $method, $parameters);
|
||||
}
|
||||
/**
|
||||
* Convert array, int or other literal to node.
|
||||
*
|
||||
* @param mixed $data Data to convert
|
||||
*
|
||||
* @return Node
|
||||
*/
|
||||
public static function toLiteral($data): Node
|
||||
{
|
||||
return self::toNode(\var_export($data, true));
|
||||
}
|
||||
/**
|
||||
* Convert code to node.
|
||||
*
|
||||
* @param string $code Code
|
||||
*
|
||||
* @memoize $code
|
||||
*
|
||||
* @return Node
|
||||
*/
|
||||
public static function toNode(string $code): Node
|
||||
{
|
||||
$res = (new ParserFactory)->create(ParserFactory::PREFER_PHP7)->parse('<?php '.$code);
|
||||
if ($res === null || empty($res) || !$res[0] instanceof Expression || !isset($res[0]->expr)) {
|
||||
throw new \RuntimeException('Invalid code was provided!');
|
||||
}
|
||||
return $res[0]->expr;
|
||||
}
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
|
|
|
@ -4,6 +4,7 @@ namespace Phabel\PluginGraph;
|
|||
|
||||
use Phabel\Plugin;
|
||||
use Phabel\PluginInterface;
|
||||
use SplQueue;
|
||||
|
||||
/**
|
||||
* Graph API wrapper.
|
||||
|
@ -54,7 +55,7 @@ class Graph
|
|||
*
|
||||
* @return SplQueue<SplQueue<Plugin>>
|
||||
*/
|
||||
public function flatten(): \SplQueue
|
||||
public function flatten(): SplQueue
|
||||
{
|
||||
return $this->graph->flatten();
|
||||
}
|
||||
|
|
|
@ -107,11 +107,11 @@ class GraphInternal
|
|||
*
|
||||
* @return SplQueue<SplQueue<Plugin>>
|
||||
*/
|
||||
public function flatten(): \SplQueue
|
||||
public function flatten(): SplQueue
|
||||
{
|
||||
if (!$this->plugins) {
|
||||
/** @var SplQueue<SplQueue<Plugin>> */
|
||||
return new \SplQueue;
|
||||
return new SplQueue;
|
||||
}
|
||||
if ($this->unlinkedNodes->count()) {
|
||||
foreach ($this->unlinkedNodes as $node) {
|
||||
|
|
|
@ -0,0 +1,219 @@
|
|||
<?php
|
||||
|
||||
namespace Phabel;
|
||||
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Arg;
|
||||
use PhpParser\Node\Expr;
|
||||
use PhpParser\Node\Expr\ArrayDimFetch;
|
||||
use PhpParser\Node\Expr\Assign;
|
||||
use PhpParser\Node\Expr\AssignOp;
|
||||
use PhpParser\Node\Expr\AssignRef;
|
||||
use PhpParser\Node\Expr\Cast\String_;
|
||||
use PhpParser\Node\Expr\Clone_;
|
||||
use PhpParser\Node\Expr\Eval_;
|
||||
use PhpParser\Node\Expr\FuncCall;
|
||||
use PhpParser\Node\Expr\Include_;
|
||||
use PhpParser\Node\Expr\List_;
|
||||
use PhpParser\Node\Expr\MethodCall;
|
||||
use PhpParser\Node\Expr\New_;
|
||||
use PhpParser\Node\Expr\NullsafeMethodCall;
|
||||
use PhpParser\Node\Expr\NullsafePropertyFetch;
|
||||
use PhpParser\Node\Expr\PostDec;
|
||||
use PhpParser\Node\Expr\PostInc;
|
||||
use PhpParser\Node\Expr\PreDec;
|
||||
use PhpParser\Node\Expr\PreInc;
|
||||
use PhpParser\Node\Expr\PropertyFetch;
|
||||
use PhpParser\Node\Expr\StaticCall;
|
||||
use PhpParser\Node\Expr\Variable;
|
||||
use PhpParser\Node\Expr\Yield_;
|
||||
use PhpParser\Node\Expr\YieldFrom;
|
||||
use PhpParser\Node\Name;
|
||||
use PhpParser\Node\Stmt\Expression;
|
||||
use PhpParser\ParserFactory;
|
||||
use ReflectionClass;
|
||||
|
||||
/**
|
||||
* Various tools.
|
||||
*
|
||||
* @author Daniil Gentili <daniil@daniil.it>
|
||||
* @license MIT
|
||||
*/
|
||||
abstract class Tools
|
||||
{
|
||||
/**
|
||||
* Replace node of one type with another.
|
||||
*
|
||||
* @param Node $node Original node
|
||||
* @param string $class Class of new node
|
||||
* @param array $propertyMap Property map between old and new objects
|
||||
*
|
||||
* @psalm-param class-string<Node> $class Class of new node
|
||||
* @psalm-param array<string, string> $propertyMap Property map between old and new objects
|
||||
*
|
||||
* @return Node
|
||||
*/
|
||||
public static function replaceType(Node $node, string $class, array $propertyMap = []): Node
|
||||
{
|
||||
if ($propertyMap) {
|
||||
$nodeNew = (new ReflectionClass($class))->newInstanceWithoutConstructor();
|
||||
foreach ($propertyMap as $old => $new) {
|
||||
$nodeNew->{$new} = $node->{$old};
|
||||
}
|
||||
$nodeNew->setAttributes($node->getAttributes());
|
||||
return $nodeNew;
|
||||
}
|
||||
return new $class(
|
||||
...\array_map(fn (string $name) => $node->{$name}, $node->getSubNodeNames()),
|
||||
$node->getAttributes()
|
||||
);
|
||||
}
|
||||
/**
|
||||
* Replace type in-place.
|
||||
*
|
||||
* @param Node &$node Original node
|
||||
* @param string $class Class of new node
|
||||
* @param array $propertyMap Property map between old and new objects
|
||||
*
|
||||
* @psalm-param class-string<Node> $class Class of new node
|
||||
* @psalm-param array<string, string> $propertyMap Property map between old and new objects
|
||||
*
|
||||
* @param-out Node &$node
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function replaceTypeInPlace(Node &$node, string $class, array $propertyMap = []): void
|
||||
{
|
||||
$node = self::replaceType($node, $class, $propertyMap);
|
||||
}
|
||||
/**
|
||||
* Create variable assignment.
|
||||
*
|
||||
* @param Variable $name Variable
|
||||
* @param Expr $expression Expression
|
||||
*
|
||||
* @return Expression
|
||||
*/
|
||||
public static function assign(Variable $name, Expr $expression): Expression
|
||||
{
|
||||
return new Expression(
|
||||
new Assign(
|
||||
$name,
|
||||
$expression
|
||||
)
|
||||
);
|
||||
}
|
||||
/**
|
||||
* Call function.
|
||||
*
|
||||
* @param array{0: class-string, 1: string}|callable-string $name Function name
|
||||
* @param Expr|Arg ...$parameters Parameters
|
||||
*
|
||||
* @return FuncCall|StaticCall
|
||||
*/
|
||||
public static function call($name, ...$parameters)
|
||||
{
|
||||
$parameters = \array_map(fn ($data) => $data instanceof Arg ? $data : new Arg($data), $parameters);
|
||||
return \is_array($name)
|
||||
? new StaticCall(new Name($name[0]), $name[1], $parameters)
|
||||
: new FuncCall(new Name($name), $parameters);
|
||||
}
|
||||
/**
|
||||
* Call method of object.
|
||||
*
|
||||
* @param Expr $name Object name
|
||||
* @param string $method Method
|
||||
* @param Expr|Arg ...$parameters Parameters
|
||||
*
|
||||
* @return MethodCall
|
||||
*/
|
||||
public static function callMethod(Expr $name, string $method, ...$parameters): MethodCall
|
||||
{
|
||||
$parameters = \array_map(fn ($data) => $data instanceof Arg ? $data : new Arg($data), $parameters);
|
||||
return new MethodCall($name, $method, $parameters);
|
||||
}
|
||||
/**
|
||||
* Convert array, int or other literal to node.
|
||||
*
|
||||
* @param mixed $data Data to convert
|
||||
*
|
||||
* @return Node
|
||||
*/
|
||||
public static function toLiteral($data): Node
|
||||
{
|
||||
return self::toNode(\var_export($data, true));
|
||||
}
|
||||
/**
|
||||
* Convert code to node.
|
||||
*
|
||||
* @param string $code Code
|
||||
*
|
||||
* @memoize $code
|
||||
*
|
||||
* @return Node
|
||||
*/
|
||||
public static function toNode(string $code): Node
|
||||
{
|
||||
$res = (new ParserFactory)->create(ParserFactory::PREFER_PHP7)->parse('<?php '.$code);
|
||||
if ($res === null || empty($res) || !$res[0] instanceof Expression || !isset($res[0]->expr)) {
|
||||
throw new \RuntimeException('Invalid code was provided!');
|
||||
}
|
||||
return $res[0]->expr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this node or any child node have any side effects (like calling other methods, or assigning variables).
|
||||
*
|
||||
* @param ?Expr $node Node
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function hasSideEffects(?Expr $node): bool
|
||||
{
|
||||
if (!$node) {
|
||||
return false;
|
||||
}
|
||||
if ($node->hasAttribute('hasSideEffects')
|
||||
|| $node instanceof String_ // __toString
|
||||
|| $node instanceof ArrayDimFetch // offsetSet/offsetGet
|
||||
|| $node instanceof Assign
|
||||
|| $node instanceof AssignOp
|
||||
|| $node instanceof AssignRef
|
||||
|| $node instanceof Clone_ // __clone
|
||||
|| $node instanceof Eval_
|
||||
|| $node instanceof FuncCall
|
||||
|| $node instanceof Include_
|
||||
|| $node instanceof List_ // offsetGet/offsetSet
|
||||
|| $node instanceof New_
|
||||
|| $node instanceof NullsafeMethodCall
|
||||
|| $node instanceof NullsafePropertyFetch
|
||||
|| $node instanceof PostDec
|
||||
|| $node instanceof PostInc
|
||||
|| $node instanceof PreDec
|
||||
|| $node instanceof PreInc
|
||||
|| $node instanceof PropertyFetch
|
||||
|| $node instanceof StaticCall
|
||||
|| $node instanceof Yield_
|
||||
|| $node instanceof YieldFrom
|
||||
) {
|
||||
$node->setAttribute('hasSideEffects', true);
|
||||
return true;
|
||||
}
|
||||
foreach ($node->getAttributes() as $name) {
|
||||
if ($node->{$name} instanceof Expr) {
|
||||
if (self::hasSideEffects($node->{$name})) {
|
||||
$node->setAttribute('hasSideEffects', true);
|
||||
return true;
|
||||
}
|
||||
} elseif (\is_array($node->{$name})) {
|
||||
foreach ($node->{$name} as $var) {
|
||||
if ($var instanceof Expr && self::hasSideEffects($var)) {
|
||||
$node->setAttribute('hasSideEffects', true);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -28,9 +28,9 @@ class Traverser
|
|||
/**
|
||||
* Plugin queue for specific package.
|
||||
*
|
||||
* @return SplQueue<SplQueue<Plugin>>
|
||||
* @return SplQueue<SplQueue<Plugin>>|null
|
||||
*/
|
||||
private SplQueue $packageQueue;
|
||||
private ?SplQueue $packageQueue;
|
||||
/**
|
||||
* Generate traverser from basic plugin instances.
|
||||
*
|
||||
|
@ -114,7 +114,7 @@ class Traverser
|
|||
} elseif (!$reducedQueue->count()) {
|
||||
return;
|
||||
}
|
||||
$ast = new RootNode($this->parser->parse(\file_get_contents($file)));
|
||||
$ast = new RootNode($this->parser->parse(\file_get_contents($file)) ?? []);
|
||||
$this->traverseAst($ast, $reducedQueue);
|
||||
}
|
||||
/**
|
||||
|
|
Loading…
Reference in New Issue