upgraded dependencies

This commit is contained in:
RafficMohammed
2023-01-08 01:59:16 +05:30
parent 51056e3aad
commit f9ae387337
6895 changed files with 133617 additions and 178680 deletions

View File

@@ -3,7 +3,7 @@
/*
* This file is part of Psy Shell.
*
* (c) 2012-2018 Justin Hileman
* (c) 2012-2022 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -25,9 +25,11 @@ class AbstractClassPass extends CodeCleanerPass
private $abstractMethods;
/**
* @throws RuntimeException if the node is an abstract function with a body
* @throws FatalErrorException if the node is an abstract function with a body
*
* @param Node $node
*
* @return int|Node|null Replacement node (or special return value)
*/
public function enterNode(Node $node)
{
@@ -41,16 +43,18 @@ class AbstractClassPass extends CodeCleanerPass
if ($node->stmts !== null) {
$msg = \sprintf('Abstract function %s cannot contain body', $name);
throw new FatalErrorException($msg, 0, E_ERROR, null, $node->getLine());
throw new FatalErrorException($msg, 0, \E_ERROR, null, $node->getLine());
}
}
}
}
/**
* @throws RuntimeException if the node is a non-abstract class with abstract methods
* @throws FatalErrorException if the node is a non-abstract class with abstract methods
*
* @param Node $node
*
* @return int|Node|Node[]|null Replacement node (or special return value)
*/
public function leaveNode(Node $node)
{
@@ -64,7 +68,7 @@ class AbstractClassPass extends CodeCleanerPass
($count === 1) ? '' : 's',
\implode(', ', $this->abstractMethods)
);
throw new FatalErrorException($msg, 0, E_ERROR, null, $node->getLine());
throw new FatalErrorException($msg, 0, \E_ERROR, null, $node->getLine());
}
}
}

View File

@@ -3,7 +3,7 @@
/*
* This file is part of Psy Shell.
*
* (c) 2012-2018 Justin Hileman
* (c) 2012-2022 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -26,14 +26,16 @@ class AssignThisVariablePass extends CodeCleanerPass
/**
* Validate that the user input does not assign the `$this` variable.
*
* @throws RuntimeException if the user assign the `$this` variable
* @throws FatalErrorException if the user assign the `$this` variable
*
* @param Node $node
*
* @return int|Node|null Replacement node (or special return value)
*/
public function enterNode(Node $node)
{
if ($node instanceof Assign && $node->var instanceof Variable && $node->var->name === 'this') {
throw new FatalErrorException('Cannot re-assign $this', 0, E_ERROR, null, $node->getLine());
throw new FatalErrorException('Cannot re-assign $this', 0, \E_ERROR, null, $node->getLine());
}
}
}

View File

@@ -3,7 +3,7 @@
/*
* This file is part of Psy Shell.
*
* (c) 2012-2018 Justin Hileman
* (c) 2012-2022 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -15,6 +15,7 @@ use PhpParser\Node;
use PhpParser\Node\Expr\FuncCall;
use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Expr\StaticCall;
use PhpParser\Node\VariadicPlaceholder;
use Psy\Exception\FatalErrorException;
/**
@@ -31,9 +32,11 @@ class CallTimePassByReferencePass extends CodeCleanerPass
/**
* Validate of use call-time pass-by-reference.
*
* @throws RuntimeException if the user used call-time pass-by-reference
* @throws FatalErrorException if the user used call-time pass-by-reference
*
* @param Node $node
*
* @return int|Node|null Replacement node (or special return value)
*/
public function enterNode(Node $node)
{
@@ -42,8 +45,12 @@ class CallTimePassByReferencePass extends CodeCleanerPass
}
foreach ($node->args as $arg) {
if ($arg instanceof VariadicPlaceholder) {
continue;
}
if ($arg->byRef) {
throw new FatalErrorException(self::EXCEPTION_MESSAGE, 0, E_ERROR, null, $node->getLine());
throw new FatalErrorException(self::EXCEPTION_MESSAGE, 0, \E_ERROR, null, $node->getLine());
}
}
}

View File

@@ -3,7 +3,7 @@
/*
* This file is part of Psy Shell.
*
* (c) 2012-2018 Justin Hileman
* (c) 2012-2022 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -17,6 +17,7 @@ use PhpParser\Node\Expr\FuncCall;
use PhpParser\Node\Name;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\Trait_;
use PhpParser\Node\VariadicPlaceholder;
use Psy\Exception\ErrorException;
/**
@@ -29,6 +30,8 @@ class CalledClassPass extends CodeCleanerPass
/**
* @param array $nodes
*
* @return Node[]|null Array of nodes
*/
public function beforeTraverse(array $nodes)
{
@@ -39,6 +42,8 @@ class CalledClassPass extends CodeCleanerPass
* @throws ErrorException if get_class or get_called_class is called without an object from outside a class
*
* @param Node $node
*
* @return int|Node|null Replacement node (or special return value)
*/
public function enterNode(Node $node)
{
@@ -61,13 +66,15 @@ class CalledClassPass extends CodeCleanerPass
$name = \strtolower($node->name);
if (\in_array($name, ['get_class', 'get_called_class'])) {
$msg = \sprintf('%s() called without object from outside a class', $name);
throw new ErrorException($msg, 0, E_USER_WARNING, null, $node->getLine());
throw new ErrorException($msg, 0, \E_USER_WARNING, null, $node->getLine());
}
}
}
/**
* @param Node $node
*
* @return int|Node|Node[]|null Replacement node (or special return value)
*/
public function leaveNode(Node $node)
{
@@ -76,8 +83,12 @@ class CalledClassPass extends CodeCleanerPass
}
}
private function isNull(Node $node)
private function isNull(Node $node): bool
{
if ($node instanceof VariadicPlaceholder) {
return false;
}
return $node->value instanceof ConstFetch && \strtolower($node->value->name) === 'null';
}
}

View File

@@ -3,7 +3,7 @@
/*
* This file is part of Psy Shell.
*
* (c) 2012-2018 Justin Hileman
* (c) 2012-2022 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.

View File

@@ -0,0 +1,55 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2022 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\CodeCleaner;
use PhpParser\Node;
use PhpParser\Node\Expr\ArrayDimFetch;
use PhpParser\Node\Expr\Assign;
use Psy\Exception\FatalErrorException;
/**
* Validate empty brackets are only used for assignment.
*/
class EmptyArrayDimFetchPass extends CodeCleanerPass
{
const EXCEPTION_MESSAGE = 'Cannot use [] for reading';
private $theseOnesAreFine = [];
/**
* @return Node[]|null Array of nodes
*/
public function beforeTraverse(array $nodes)
{
$this->theseOnesAreFine = [];
}
/**
* @throws FatalErrorException if the user used empty empty array dim fetch outside of assignment
*
* @param Node $node
*
* @return int|Node|null Replacement node (or special return value)
*/
public function enterNode(Node $node)
{
if ($node instanceof Assign && $node->var instanceof ArrayDimFetch) {
$this->theseOnesAreFine[] = $node->var;
}
if ($node instanceof ArrayDimFetch && $node->dim === null) {
if (!\in_array($node, $this->theseOnesAreFine)) {
throw new FatalErrorException(self::EXCEPTION_MESSAGE, $node->getLine());
}
}
}
}

View File

@@ -3,7 +3,7 @@
/*
* This file is part of Psy Shell.
*
* (c) 2012-2018 Justin Hileman
* (c) 2012-2022 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -15,6 +15,7 @@ use PhpParser\Node;
use PhpParser\Node\Expr\Exit_;
use PhpParser\Node\Expr\StaticCall;
use PhpParser\Node\Name\FullyQualified as FullyQualifiedName;
use Psy\Exception\BreakException;
class ExitPass extends CodeCleanerPass
{
@@ -22,11 +23,13 @@ class ExitPass extends CodeCleanerPass
* Converts exit calls to BreakExceptions.
*
* @param \PhpParser\Node $node
*
* @return int|Node|Node[]|null Replacement node (or special return value)
*/
public function leaveNode(Node $node)
{
if ($node instanceof Exit_) {
return new StaticCall(new FullyQualifiedName('Psy\Exception\BreakException'), 'exitShell');
return new StaticCall(new FullyQualifiedName(BreakException::class), 'exitShell');
}
}
}

View File

@@ -3,7 +3,7 @@
/*
* This file is part of Psy Shell.
*
* (c) 2012-2018 Justin Hileman
* (c) 2012-2022 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -24,6 +24,8 @@ class FinalClassPass extends CodeCleanerPass
/**
* @param array $nodes
*
* @return Node[]|null Array of nodes
*/
public function beforeTraverse(array $nodes)
{
@@ -31,9 +33,11 @@ class FinalClassPass extends CodeCleanerPass
}
/**
* @throws RuntimeException if the node is a class that extends a final class
* @throws FatalErrorException if the node is a class that extends a final class
*
* @param Node $node
*
* @return int|Node|null Replacement node (or special return value)
*/
public function enterNode(Node $node)
{
@@ -42,7 +46,7 @@ class FinalClassPass extends CodeCleanerPass
$extends = (string) $node->extends;
if ($this->isFinalClass($extends)) {
$msg = \sprintf('Class %s may not inherit from final class (%s)', $node->name, $extends);
throw new FatalErrorException($msg, 0, E_ERROR, null, $node->getLine());
throw new FatalErrorException($msg, 0, \E_ERROR, null, $node->getLine());
}
}
@@ -57,7 +61,7 @@ class FinalClassPass extends CodeCleanerPass
*
* @return bool
*/
private function isFinalClass($name)
private function isFinalClass(string $name): bool
{
if (!\class_exists($name)) {
return isset($this->finalClasses[\strtolower($name)]);

View File

@@ -3,7 +3,7 @@
/*
* This file is part of Psy Shell.
*
* (c) 2012-2018 Justin Hileman
* (c) 2012-2022 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -23,12 +23,17 @@ class FunctionContextPass extends CodeCleanerPass
/**
* @param array $nodes
*
* @return Node[]|null Array of nodes
*/
public function beforeTraverse(array $nodes)
{
$this->functionDepth = 0;
}
/**
* @return int|Node|null Replacement node (or special return value)
*/
public function enterNode(Node $node)
{
if ($node instanceof FunctionLike) {
@@ -45,12 +50,14 @@ class FunctionContextPass extends CodeCleanerPass
// It causes fatal error.
if ($node instanceof Yield_) {
$msg = 'The "yield" expression can only be used inside a function';
throw new FatalErrorException($msg, 0, E_ERROR, null, $node->getLine());
throw new FatalErrorException($msg, 0, \E_ERROR, null, $node->getLine());
}
}
/**
* @param \PhpParser\Node $node
*
* @return int|Node|Node[]|null Replacement node (or special return value)
*/
public function leaveNode(Node $node)
{

View File

@@ -3,7 +3,7 @@
/*
* This file is part of Psy Shell.
*
* (c) 2012-2018 Justin Hileman
* (c) 2012-2022 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -14,12 +14,12 @@ namespace Psy\CodeCleaner;
use PhpParser\Node;
use PhpParser\Node\Expr\Array_;
use PhpParser\Node\Expr\Assign;
use PhpParser\Node\Expr\Empty_;
use PhpParser\Node\Expr\FuncCall;
use PhpParser\Node\Expr\Isset_;
use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Expr\StaticCall;
use PhpParser\Node\Stmt\Unset_;
use PhpParser\Node\VariadicPlaceholder;
use Psy\Exception\FatalErrorException;
/**
@@ -29,33 +29,31 @@ use Psy\Exception\FatalErrorException;
*/
class FunctionReturnInWriteContextPass extends CodeCleanerPass
{
const PHP55_MESSAGE = 'Cannot use isset() on the result of a function call (you can use "null !== func()" instead)';
const ISSET_MESSAGE = 'Cannot use isset() on the result of an expression (you can use "null !== expression" instead)';
const EXCEPTION_MESSAGE = "Can't use function return value in write context";
private $atLeastPhp55;
public function __construct()
{
$this->atLeastPhp55 = \version_compare(PHP_VERSION, '5.5', '>=');
}
/**
* Validate that the functions are used correctly.
*
* @throws FatalErrorException if a function is passed as an argument reference
* @throws FatalErrorException if a function is used as an argument in the isset
* @throws FatalErrorException if a function is used as an argument in the empty, only for PHP < 5.5
* @throws FatalErrorException if a value is assigned to a function
*
* @param Node $node
*
* @return int|Node|null Replacement node (or special return value)
*/
public function enterNode(Node $node)
{
if ($node instanceof Array_ || $this->isCallNode($node)) {
$items = $node instanceof Array_ ? $node->items : $node->args;
foreach ($items as $item) {
if ($item instanceof VariadicPlaceholder) {
continue;
}
if ($item && $item->byRef && $this->isCallNode($item->value)) {
throw new FatalErrorException(self::EXCEPTION_MESSAGE, 0, E_ERROR, null, $node->getLine());
throw new FatalErrorException(self::EXCEPTION_MESSAGE, 0, \E_ERROR, null, $node->getLine());
}
}
} elseif ($node instanceof Isset_ || $node instanceof Unset_) {
@@ -64,17 +62,15 @@ class FunctionReturnInWriteContextPass extends CodeCleanerPass
continue;
}
$msg = ($node instanceof Isset_ && $this->atLeastPhp55) ? self::PHP55_MESSAGE : self::EXCEPTION_MESSAGE;
throw new FatalErrorException($msg, 0, E_ERROR, null, $node->getLine());
$msg = $node instanceof Isset_ ? self::ISSET_MESSAGE : self::EXCEPTION_MESSAGE;
throw new FatalErrorException($msg, 0, \E_ERROR, null, $node->getLine());
}
} elseif ($node instanceof Empty_ && !$this->atLeastPhp55 && $this->isCallNode($node->expr)) {
throw new FatalErrorException(self::EXCEPTION_MESSAGE, 0, E_ERROR, null, $node->getLine()); // @codeCoverageIgnore
} elseif ($node instanceof Assign && $this->isCallNode($node->var)) {
throw new FatalErrorException(self::EXCEPTION_MESSAGE, 0, E_ERROR, null, $node->getLine());
throw new FatalErrorException(self::EXCEPTION_MESSAGE, 0, \E_ERROR, null, $node->getLine());
}
}
private function isCallNode(Node $node)
private function isCallNode(Node $node): bool
{
return $node instanceof FuncCall || $node instanceof MethodCall || $node instanceof StaticCall;
}

View File

@@ -3,7 +3,7 @@
/*
* This file is part of Psy Shell.
*
* (c) 2012-2018 Justin Hileman
* (c) 2012-2022 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -32,7 +32,7 @@ class ImplicitReturnPass extends CodeCleanerPass
*
* @return array
*/
public function beforeTraverse(array $nodes)
public function beforeTraverse(array $nodes): array
{
return $this->addImplicitReturn($nodes);
}
@@ -42,7 +42,7 @@ class ImplicitReturnPass extends CodeCleanerPass
*
* @return array
*/
private function addImplicitReturn(array $nodes)
private function addImplicitReturn(array $nodes): array
{
// If nodes is empty, it can't have a return value.
if (empty($nodes)) {
@@ -118,7 +118,7 @@ class ImplicitReturnPass extends CodeCleanerPass
*
* @return bool
*/
private static function isNonExpressionStmt(Node $node)
private static function isNonExpressionStmt(Node $node): bool
{
return $node instanceof Stmt &&
!$node instanceof Expression &&

View File

@@ -3,7 +3,7 @@
/*
* This file is part of Psy Shell.
*
* (c) 2012-2018 Justin Hileman
* (c) 2012-2022 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -12,6 +12,9 @@
namespace Psy\CodeCleaner;
use PhpParser\Node;
use PhpParser\Node\Expr\Array_;
use PhpParser\Node\Expr\BinaryOp;
use PhpParser\Node\Expr\ClassConstFetch;
use PhpParser\Node\Expr\ConstFetch;
use PhpParser\Node\Expr\Instanceof_;
use PhpParser\Node\Scalar;
@@ -27,21 +30,40 @@ class InstanceOfPass extends CodeCleanerPass
{
const EXCEPTION_MSG = 'instanceof expects an object instance, constant given';
private $atLeastPhp73;
public function __construct()
{
$this->atLeastPhp73 = \version_compare(\PHP_VERSION, '7.3', '>=');
}
/**
* Validate that the instanceof statement does not receive a scalar value or a non-class constant.
*
* @throws FatalErrorException if a scalar or a non-class constant is given
*
* @param Node $node
*
* @return int|Node|null Replacement node (or special return value)
*/
public function enterNode(Node $node)
{
// Basically everything is allowed in PHP 7.3 :)
if ($this->atLeastPhp73) {
return;
}
if (!$node instanceof Instanceof_) {
return;
}
if (($node->expr instanceof Scalar && !$node->expr instanceof Encapsed) || $node->expr instanceof ConstFetch) {
throw new FatalErrorException(self::EXCEPTION_MSG, 0, E_ERROR, null, $node->getLine());
if (($node->expr instanceof Scalar && !$node->expr instanceof Encapsed) ||
$node->expr instanceof BinaryOp ||
$node->expr instanceof Array_ ||
$node->expr instanceof ConstFetch ||
$node->expr instanceof ClassConstFetch
) {
throw new FatalErrorException(self::EXCEPTION_MSG, 0, \E_ERROR, null, $node->getLine());
}
}
}

View File

@@ -0,0 +1,49 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2022 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\CodeCleaner;
use PhpParser\Node;
use PhpParser\Node\Expr\ArrayDimFetch;
use PhpParser\Node\Expr\Isset_;
use PhpParser\Node\Expr\NullsafePropertyFetch;
use PhpParser\Node\Expr\PropertyFetch;
use PhpParser\Node\Expr\Variable;
use Psy\Exception\FatalErrorException;
/**
* Code cleaner pass to ensure we only allow variables, array fetch and property
* fetch expressions in isset() calls.
*/
class IssetPass extends CodeCleanerPass
{
const EXCEPTION_MSG = 'Cannot use isset() on the result of an expression (you can use "null !== expression" instead)';
/**
* @throws FatalErrorException
*
* @param Node $node
*
* @return int|Node|null Replacement node (or special return value)
*/
public function enterNode(Node $node)
{
if (!$node instanceof Isset_) {
return;
}
foreach ($node->vars as $var) {
if (!$var instanceof Variable && !$var instanceof ArrayDimFetch && !$var instanceof PropertyFetch && !$var instanceof NullsafePropertyFetch) {
throw new FatalErrorException(self::EXCEPTION_MSG, 0, \E_ERROR, null, $node->getLine());
}
}
}
}

View File

@@ -0,0 +1,101 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2022 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\CodeCleaner;
use PhpParser\Node;
use PhpParser\Node\FunctionLike;
use PhpParser\Node\Stmt\Goto_;
use PhpParser\Node\Stmt\Label;
use Psy\Exception\FatalErrorException;
/**
* CodeCleanerPass for label context.
*
* This class partially emulates the PHP label specification.
* PsySH can not declare labels by sequentially executing lines with eval,
* but since it is not a syntax error, no error is raised.
* This class warns before invalid goto causes a fatal error.
* Since this is a simple checker, it does not block real fatal error
* with complex syntax. (ex. it does not parse inside function.)
*
* @see http://php.net/goto
*/
class LabelContextPass extends CodeCleanerPass
{
/** @var int */
private $functionDepth;
/** @var array */
private $labelDeclarations;
/** @var array */
private $labelGotos;
/**
* @param array $nodes
*
* @return Node[]|null Array of nodes
*/
public function beforeTraverse(array $nodes)
{
$this->functionDepth = 0;
$this->labelDeclarations = [];
$this->labelGotos = [];
}
/**
* @return int|Node|null Replacement node (or special return value)
*/
public function enterNode(Node $node)
{
if ($node instanceof FunctionLike) {
$this->functionDepth++;
return;
}
// node is inside function context
if ($this->functionDepth !== 0) {
return;
}
if ($node instanceof Goto_) {
$this->labelGotos[\strtolower($node->name)] = $node->getLine();
} elseif ($node instanceof Label) {
$this->labelDeclarations[\strtolower($node->name)] = $node->getLine();
}
}
/**
* @param \PhpParser\Node $node
*
* @return int|Node|Node[]|null Replacement node (or special return value)
*/
public function leaveNode(Node $node)
{
if ($node instanceof FunctionLike) {
$this->functionDepth--;
}
}
/**
* @return Node[]|null Array of nodes
*/
public function afterTraverse(array $nodes)
{
foreach ($this->labelGotos as $name => $line) {
if (!isset($this->labelDeclarations[$name])) {
$msg = "'goto' to undefined label '{$name}'";
throw new FatalErrorException($msg, 0, \E_ERROR, null, $line);
}
}
}
}

View File

@@ -3,7 +3,7 @@
/*
* This file is part of Psy Shell.
*
* (c) 2012-2018 Justin Hileman
* (c) 2012-2022 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -26,6 +26,8 @@ class LeavePsyshAlonePass extends CodeCleanerPass
* @throws RuntimeException if the user is messing with $__psysh__
*
* @param Node $node
*
* @return int|Node|null Replacement node (or special return value)
*/
public function enterNode(Node $node)
{

View File

@@ -1,73 +0,0 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2018 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\CodeCleaner;
use PhpParser\Node;
use PhpParser\Node\Expr\Empty_;
use PhpParser\Node\Expr\Variable;
use Psy\Exception\ParseErrorException;
/**
* Validate that the user did not call the language construct `empty()` on a
* statement in PHP < 5.5.
*
* @codeCoverageIgnore
*/
class LegacyEmptyPass extends CodeCleanerPass
{
private $atLeastPhp55;
public function __construct()
{
$this->atLeastPhp55 = \version_compare(PHP_VERSION, '5.5', '>=');
}
/**
* Validate use of empty in PHP < 5.5.
*
* @throws ParseErrorException if the user used empty with anything but a variable
*
* @param Node $node
*/
public function enterNode(Node $node)
{
if ($this->atLeastPhp55) {
return;
}
if (!$node instanceof Empty_) {
return;
}
if (!$node->expr instanceof Variable) {
$msg = \sprintf('syntax error, unexpected %s', $this->getUnexpectedThing($node->expr));
throw new ParseErrorException($msg, $node->expr->getLine());
}
}
private function getUnexpectedThing(Node $node)
{
switch ($node->getType()) {
case 'Scalar_String':
case 'Scalar_LNumber':
case 'Scalar_DNumber':
return \json_encode($node->value);
case 'Expr_ConstFetch':
return (string) $node->name;
default:
return $node->getType();
}
}
}

View File

@@ -3,7 +3,7 @@
/*
* This file is part of Psy Shell.
*
* (c) 2012-2018 Justin Hileman
* (c) 2012-2022 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -33,7 +33,7 @@ class ListPass extends CodeCleanerPass
public function __construct()
{
$this->atLeastPhp71 = \version_compare(PHP_VERSION, '7.1', '>=');
$this->atLeastPhp71 = \version_compare(\PHP_VERSION, '7.1', '>=');
}
/**
@@ -42,6 +42,8 @@ class ListPass extends CodeCleanerPass
* @throws ParseErrorException if the user used empty with anything but a variable
*
* @param Node $node
*
* @return int|Node|null Replacement node (or special return value)
*/
public function enterNode(Node $node)
{
@@ -97,7 +99,7 @@ class ListPass extends CodeCleanerPass
*
* @return bool
*/
private static function isValidArrayItem(Expr $item)
private static function isValidArrayItem(Expr $item): bool
{
$value = ($item instanceof ArrayItem) ? $item->value : $item;

View File

@@ -3,7 +3,7 @@
/*
* This file is part of Psy Shell.
*
* (c) 2012-2018 Justin Hileman
* (c) 2012-2022 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -32,6 +32,8 @@ class LoopContextPass extends CodeCleanerPass
/**
* {@inheritdoc}
*
* @return Node[]|null Array of nodes
*/
public function beforeTraverse(array $nodes)
{
@@ -45,6 +47,8 @@ class LoopContextPass extends CodeCleanerPass
* @throws FatalErrorException if the node is a break or continue and has an argument less than 1
*
* @param Node $node
*
* @return int|Node|null Replacement node (or special return value)
*/
public function enterNode(Node $node)
{
@@ -63,23 +67,23 @@ class LoopContextPass extends CodeCleanerPass
if ($this->loopDepth === 0) {
$msg = \sprintf("'%s' not in the 'loop' or 'switch' context", $operator);
throw new FatalErrorException($msg, 0, E_ERROR, null, $node->getLine());
throw new FatalErrorException($msg, 0, \E_ERROR, null, $node->getLine());
}
if ($node->num instanceof LNumber || $node->num instanceof DNumber) {
$num = $node->num->value;
if ($node->num instanceof DNumber || $num < 1) {
$msg = \sprintf("'%s' operator accepts only positive numbers", $operator);
throw new FatalErrorException($msg, 0, E_ERROR, null, $node->getLine());
throw new FatalErrorException($msg, 0, \E_ERROR, null, $node->getLine());
}
if ($num > $this->loopDepth) {
$msg = \sprintf("Cannot '%s' %d levels", $operator, $num);
throw new FatalErrorException($msg, 0, E_ERROR, null, $node->getLine());
throw new FatalErrorException($msg, 0, \E_ERROR, null, $node->getLine());
}
} elseif ($node->num) {
$msg = \sprintf("'%s' operator with non-constant operand is no longer supported", $operator);
throw new FatalErrorException($msg, 0, E_ERROR, null, $node->getLine());
throw new FatalErrorException($msg, 0, \E_ERROR, null, $node->getLine());
}
break;
}
@@ -87,6 +91,8 @@ class LoopContextPass extends CodeCleanerPass
/**
* @param Node $node
*
* @return int|Node|Node[]|null Replacement node (or special return value)
*/
public function leaveNode(Node $node)
{

View File

@@ -3,7 +3,7 @@
/*
* This file is part of Psy Shell.
*
* (c) 2012-2018 Justin Hileman
* (c) 2012-2022 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -29,7 +29,7 @@ class MagicConstantsPass extends CodeCleanerPass
*
* @param Node $node
*
* @return null|FuncCall|String_
* @return FuncCall|String_|null
*/
public function enterNode(Node $node)
{

View File

@@ -3,7 +3,7 @@
/*
* This file is part of Psy Shell.
*
* (c) 2012-2018 Justin Hileman
* (c) 2012-2022 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -29,10 +29,12 @@ abstract class NamespaceAwarePass extends CodeCleanerPass
* use afterTraverse or call parent::beforeTraverse() when overloading.
*
* Reset the namespace and the current scope before beginning analysis
*
* @return Node[]|null Array of nodes
*/
public function beforeTraverse(array $nodes)
{
$this->namespace = [];
$this->namespace = [];
$this->currentScope = [];
}
@@ -41,6 +43,8 @@ abstract class NamespaceAwarePass extends CodeCleanerPass
* leaveNode or call parent::enterNode() when overloading
*
* @param Node $node
*
* @return int|Node|null Replacement node (or special return value)
*/
public function enterNode(Node $node)
{
@@ -56,7 +60,7 @@ abstract class NamespaceAwarePass extends CodeCleanerPass
*
* @return string
*/
protected function getFullyQualifiedName($name)
protected function getFullyQualifiedName($name): string
{
if ($name instanceof FullyQualifiedName) {
return \implode('\\', $name->parts);

View File

@@ -3,7 +3,7 @@
/*
* This file is part of Psy Shell.
*
* (c) 2012-2018 Justin Hileman
* (c) 2012-2022 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -11,6 +11,7 @@
namespace Psy\CodeCleaner;
use PhpParser\Node;
use PhpParser\Node\Name;
use PhpParser\Node\Stmt\Namespace_;
use Psy\CodeCleaner;
@@ -46,6 +47,8 @@ class NamespacePass extends CodeCleanerPass
* is encountered.
*
* @param array $nodes
*
* @return Node[]|null Array of nodes
*/
public function beforeTraverse(array $nodes)
{
@@ -78,7 +81,7 @@ class NamespacePass extends CodeCleanerPass
* Remember the namespace and (re)set the namespace on the CodeCleaner as
* well.
*
* @param null|Name $namespace
* @param Name|null $namespace
*/
private function setNamespace($namespace)
{

View File

@@ -3,7 +3,7 @@
/*
* This file is part of Psy Shell.
*
* (c) 2012-2018 Justin Hileman
* (c) 2012-2022 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -26,10 +26,10 @@ class NoReturnValue
/**
* Get PhpParser AST expression for creating a new NoReturnValue.
*
* @return PhpParser\Node\Expr\New_
* @return New_
*/
public static function create()
public static function create(): New_
{
return new New_(new FullyQualifiedName('Psy\CodeCleaner\NoReturnValue'));
return new New_(new FullyQualifiedName(self::class));
}
}

View File

@@ -3,7 +3,7 @@
/*
* This file is part of Psy Shell.
*
* (c) 2012-2018 Justin Hileman
* (c) 2012-2022 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -13,6 +13,8 @@ namespace Psy\CodeCleaner;
use PhpParser\Node;
use PhpParser\Node\Expr;
use PhpParser\Node\Expr\Array_;
use PhpParser\Node\Expr\ArrayDimFetch;
use PhpParser\Node\Expr\ClassConstFetch;
use PhpParser\Node\Expr\FuncCall;
use PhpParser\Node\Expr\MethodCall;
@@ -32,6 +34,8 @@ class PassableByReferencePass extends CodeCleanerPass
* @throws FatalErrorException if non-variables are passed by reference
*
* @param Node $node
*
* @return int|Node|null Replacement node (or special return value)
*/
public function enterNode(Node $node)
{
@@ -59,15 +63,20 @@ class PassableByReferencePass extends CodeCleanerPass
if (\array_key_exists($key, $node->args)) {
$arg = $node->args[$key];
if ($param->isPassedByReference() && !$this->isPassableByReference($arg)) {
throw new FatalErrorException(self::EXCEPTION_MESSAGE, 0, E_ERROR, null, $node->getLine());
throw new FatalErrorException(self::EXCEPTION_MESSAGE, 0, \E_ERROR, null, $node->getLine());
}
}
}
}
}
private function isPassableByReference(Node $arg)
private function isPassableByReference(Node $arg): bool
{
// Unpacked arrays can be passed by reference
if ($arg->value instanceof Array_) {
return $arg->unpack;
}
// FuncCall, MethodCall and StaticCall are all PHP _warnings_ not fatal errors, so we'll let
// PHP handle those ones :)
return $arg->value instanceof ClassConstFetch ||
@@ -75,7 +84,8 @@ class PassableByReferencePass extends CodeCleanerPass
$arg->value instanceof Variable ||
$arg->value instanceof FuncCall ||
$arg->value instanceof MethodCall ||
$arg->value instanceof StaticCall;
$arg->value instanceof StaticCall ||
$arg->value instanceof ArrayDimFetch;
}
/**
@@ -102,7 +112,7 @@ class PassableByReferencePass extends CodeCleanerPass
} elseif (++$nonPassable > 2) {
// There can be *at most* two non-passable-by-reference args in a row. This is about
// as close as we can get to validating the arguments for this function :-/
throw new FatalErrorException(self::EXCEPTION_MESSAGE, 0, E_ERROR, null, $node->getLine());
throw new FatalErrorException(self::EXCEPTION_MESSAGE, 0, \E_ERROR, null, $node->getLine());
}
}
}

View File

@@ -3,7 +3,7 @@
/*
* This file is part of Psy Shell.
*
* (c) 2012-2018 Justin Hileman
* (c) 2012-2022 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -19,7 +19,6 @@ use PhpParser\Node\Name\FullyQualified as FullyQualifiedName;
use PhpParser\Node\Scalar\LNumber;
use Psy\Exception\ErrorException;
use Psy\Exception\FatalErrorException;
use Psy\Shell;
/**
* Add runtime validation for `require` and `require_once` calls.
@@ -30,6 +29,8 @@ class RequirePass extends CodeCleanerPass
/**
* {@inheritdoc}
*
* @return int|Node|null Replacement node (or special return value)
*/
public function enterNode(Node $origNode)
{
@@ -49,7 +50,7 @@ class RequirePass extends CodeCleanerPass
* $foo = require \Psy\CodeCleaner\RequirePass::resolve($bar)
*/
$node->expr = new StaticCall(
new FullyQualifiedName('Psy\CodeCleaner\RequirePass'),
new FullyQualifiedName(self::class),
'resolve',
[new Arg($origNode->expr), new Arg(new LNumber($origNode->getLine()))],
$origNode->getAttributes()
@@ -63,15 +64,18 @@ class RequirePass extends CodeCleanerPass
*
* If $file can be resolved, return $file. Otherwise throw a fatal error exception.
*
* If $file collides with a path in the currently running PsySH phar, it will be resolved
* relative to the include path, to prevent PHP from grabbing the phar version of the file.
*
* @throws FatalErrorException when unable to resolve include path for $file
* @throws ErrorException if $file is empty and E_WARNING is included in error_reporting level
*
* @param string $file
* @param int $lineNumber Line number of the original require expression
*
* @return string Exactly the same as $file
* @return string Exactly the same as $file, unless $file collides with a path in the currently running phar
*/
public static function resolve($file, $lineNumber = null)
public static function resolve($file, $lineNumber = null): string
{
$file = (string) $file;
@@ -79,23 +83,51 @@ class RequirePass extends CodeCleanerPass
// @todo Shell::handleError would be better here, because we could
// fake the file and line number, but we can't call it statically.
// So we're duplicating some of the logics here.
if (E_WARNING & \error_reporting()) {
ErrorException::throwException(E_WARNING, 'Filename cannot be empty', null, $lineNumber);
if (\E_WARNING & \error_reporting()) {
ErrorException::throwException(\E_WARNING, 'Filename cannot be empty', null, $lineNumber);
}
// @todo trigger an error as fallback? this is pretty ugly…
// trigger_error('Filename cannot be empty', E_USER_WARNING);
}
if ($file === '' || !\stream_resolve_include_path($file)) {
$resolvedPath = \stream_resolve_include_path($file);
if ($file === '' || !$resolvedPath) {
$msg = \sprintf("Failed opening required '%s'", $file);
throw new FatalErrorException($msg, 0, E_ERROR, null, $lineNumber);
throw new FatalErrorException($msg, 0, \E_ERROR, null, $lineNumber);
}
// Special case: if the path is not already relative or absolute, and it would resolve to
// something inside the currently running phar (e.g. `vendor/autoload.php`), we'll resolve
// it relative to the include path so PHP won't grab the phar version.
//
// Note that this only works if the phar has `psysh` in the path. We might want to lift this
// restriction and special case paths that would collide with any running phar?
if ($resolvedPath !== $file && $file[0] !== '.') {
$runningPhar = \Phar::running();
if (\strpos($runningPhar, 'psysh') !== false && \is_file($runningPhar.\DIRECTORY_SEPARATOR.$file)) {
foreach (self::getIncludePath() as $prefix) {
$resolvedPath = $prefix.\DIRECTORY_SEPARATOR.$file;
if (\is_file($resolvedPath)) {
return $resolvedPath;
}
}
}
}
return $file;
}
private function isRequireNode(Node $node)
private function isRequireNode(Node $node): bool
{
return $node instanceof Include_ && \in_array($node->type, self::$requireTypes);
}
private static function getIncludePath(): array
{
if (\PATH_SEPARATOR === ':') {
return \preg_split('#:(?!//)#', \get_include_path());
}
return \explode(\PATH_SEPARATOR, \get_include_path());
}
}

View File

@@ -0,0 +1,127 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2022 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\CodeCleaner;
use PhpParser\Node;
use PhpParser\Node\Expr\Closure;
use PhpParser\Node\Expr\ConstFetch;
use PhpParser\Node\Identifier;
use PhpParser\Node\NullableType;
use PhpParser\Node\Stmt\Function_;
use PhpParser\Node\Stmt\Return_;
use PhpParser\Node\UnionType;
use Psy\Exception\FatalErrorException;
/**
* Add runtime validation for return types.
*/
class ReturnTypePass extends CodeCleanerPass
{
const MESSAGE = 'A function with return type must return a value';
const NULLABLE_MESSAGE = 'A function with return type must return a value (did you mean "return null;" instead of "return;"?)';
const VOID_MESSAGE = 'A void function must not return a value';
const VOID_NULL_MESSAGE = 'A void function must not return a value (did you mean "return;" instead of "return null;"?)';
const NULLABLE_VOID_MESSAGE = 'Void type cannot be nullable';
private $atLeastPhp71;
private $returnTypeStack = [];
public function __construct()
{
$this->atLeastPhp71 = \version_compare(\PHP_VERSION, '7.1', '>=');
}
/**
* {@inheritdoc}
*
* @return int|Node|null Replacement node (or special return value)
*/
public function enterNode(Node $node)
{
if (!$this->atLeastPhp71) {
return; // @codeCoverageIgnore
}
if ($this->isFunctionNode($node)) {
$this->returnTypeStack[] = $node->returnType;
return;
}
if (!empty($this->returnTypeStack) && $node instanceof Return_) {
$expectedType = \end($this->returnTypeStack);
if ($expectedType === null) {
return;
}
$msg = null;
if ($this->typeName($expectedType) === 'void') {
// Void functions
if ($expectedType instanceof NullableType) {
$msg = self::NULLABLE_VOID_MESSAGE;
} elseif ($node->expr instanceof ConstFetch && \strtolower($node->expr->name) === 'null') {
$msg = self::VOID_NULL_MESSAGE;
} elseif ($node->expr !== null) {
$msg = self::VOID_MESSAGE;
}
} else {
// Everything else
if ($node->expr === null) {
$msg = $expectedType instanceof NullableType ? self::NULLABLE_MESSAGE : self::MESSAGE;
}
}
if ($msg !== null) {
throw new FatalErrorException($msg, 0, \E_ERROR, null, $node->getLine());
}
}
}
/**
* {@inheritdoc}
*
* @return int|Node|Node[]|null Replacement node (or special return value)
*/
public function leaveNode(Node $node)
{
if (!$this->atLeastPhp71) {
return; // @codeCoverageIgnore
}
if (!empty($this->returnTypeStack) && $this->isFunctionNode($node)) {
\array_pop($this->returnTypeStack);
}
}
private function isFunctionNode(Node $node): bool
{
return $node instanceof Function_ || $node instanceof Closure;
}
private function typeName(Node $node): string
{
if ($node instanceof UnionType) {
return \implode('|', \array_map([$this, 'typeName'], $node->types));
}
if ($node instanceof NullableType) {
return \strtolower($node->type->name);
}
if ($node instanceof Identifier) {
return \strtolower($node->name);
}
throw new \InvalidArgumentException('Unable to find type name');
}
}

View File

@@ -3,7 +3,7 @@
/*
* This file is part of Psy Shell.
*
* (c) 2012-2018 Justin Hileman
* (c) 2012-2022 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -11,6 +11,7 @@
namespace Psy\CodeCleaner;
use PhpParser\Node;
use PhpParser\Node\Identifier;
use PhpParser\Node\Scalar\LNumber;
use PhpParser\Node\Stmt\Declare_;
@@ -32,12 +33,6 @@ class StrictTypesPass extends CodeCleanerPass
const EXCEPTION_MESSAGE = 'strict_types declaration must have 0 or 1 as its value';
private $strictTypes = false;
private $atLeastPhp7;
public function __construct()
{
$this->atLeastPhp7 = \version_compare(PHP_VERSION, '7.0', '>=');
}
/**
* If this is a standalone strict types declaration, remember it for later.
@@ -48,16 +43,14 @@ class StrictTypesPass extends CodeCleanerPass
* @throws FatalErrorException if an invalid `strict_types` declaration is found
*
* @param array $nodes
*
* @return Node[]|null Array of nodes
*/
public function beforeTraverse(array $nodes)
{
if (!$this->atLeastPhp7) {
return; // @codeCoverageIgnore
}
$prependStrictTypes = $this->strictTypes;
foreach ($nodes as $key => $node) {
foreach ($nodes as $node) {
if ($node instanceof Declare_) {
foreach ($node->declares as $declare) {
// For PHP Parser 4.x
@@ -65,7 +58,7 @@ class StrictTypesPass extends CodeCleanerPass
if ($declareKey === 'strict_types') {
$value = $declare->value;
if (!$value instanceof LNumber || ($value->value !== 0 && $value->value !== 1)) {
throw new FatalErrorException(self::EXCEPTION_MESSAGE, 0, E_ERROR, null, $node->getLine());
throw new FatalErrorException(self::EXCEPTION_MESSAGE, 0, \E_ERROR, null, $node->getLine());
}
$this->strictTypes = $value->value === 1;

View File

@@ -3,7 +3,7 @@
/*
* This file is part of Psy Shell.
*
* (c) 2012-2018 Justin Hileman
* (c) 2012-2022 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -17,6 +17,7 @@ use PhpParser\Node\Name\FullyQualified as FullyQualifiedName;
use PhpParser\Node\Stmt\GroupUse;
use PhpParser\Node\Stmt\Namespace_;
use PhpParser\Node\Stmt\Use_;
use PhpParser\Node\Stmt\UseUse;
use PhpParser\NodeTraverser;
/**
@@ -31,8 +32,8 @@ use PhpParser\NodeTraverser;
*/
class UseStatementPass extends CodeCleanerPass
{
private $aliases = [];
private $lastAliases = [];
private $aliases = [];
private $lastAliases = [];
private $lastNamespace = null;
/**
@@ -43,13 +44,15 @@ class UseStatementPass extends CodeCleanerPass
* work like you'd expect.
*
* @param Node $node
*
* @return int|Node|null Replacement node (or special return value)
*/
public function enterNode(Node $node)
{
if ($node instanceof Namespace_) {
// If this is the same namespace as last namespace, let's do ourselves
// a favor and reload all the aliases...
if (\strtolower($node->name) === \strtolower($this->lastNamespace)) {
if (\strtolower($node->name ?: '') === \strtolower($this->lastNamespace ?: '')) {
$this->aliases = $this->lastAliases;
}
}
@@ -62,21 +65,23 @@ class UseStatementPass extends CodeCleanerPass
* remembered aliases to the code.
*
* @param Node $node
*
* @return int|Node|Node[]|null Replacement node (or special return value)
*/
public function leaveNode(Node $node)
{
// Store a reference to every "use" statement, because we'll need them in a bit.
if ($node instanceof Use_) {
// Store a reference to every "use" statement, because we'll need
// them in a bit.
foreach ($node->uses as $use) {
$alias = $use->alias ?: \end($use->name->parts);
$this->aliases[\strtolower($alias)] = $use->name;
}
return NodeTraverser::REMOVE_NODE;
} elseif ($node instanceof GroupUse) {
// Expand every "use" statement in the group into a full, standalone
// "use" and store 'em with the others.
}
// Expand every "use" statement in the group into a full, standalone "use" and store 'em with the others.
if ($node instanceof GroupUse) {
foreach ($node->uses as $use) {
$alias = $use->alias ?: \end($use->name->parts);
$this->aliases[\strtolower($alias)] = Name::concat($node->prefix, $use->name, [
@@ -86,23 +91,32 @@ class UseStatementPass extends CodeCleanerPass
}
return NodeTraverser::REMOVE_NODE;
} elseif ($node instanceof Namespace_) {
// Start fresh, since we're done with this namespace.
}
// Start fresh, since we're done with this namespace.
if ($node instanceof Namespace_) {
$this->lastNamespace = $node->name;
$this->lastAliases = $this->aliases;
$this->aliases = [];
} else {
foreach ($node as $name => $subNode) {
if ($subNode instanceof Name) {
// Implicitly thunk all aliases.
if ($replacement = $this->findAlias($subNode)) {
$node->$name = $replacement;
}
$this->lastAliases = $this->aliases;
$this->aliases = [];
return;
}
// Do nothing with UseUse; this an entry in the list of uses in the use statement.
if ($node instanceof UseUse) {
return;
}
// For everything else, we'll implicitly thunk all aliases into fully-qualified names.
foreach ($node as $name => $subNode) {
if ($subNode instanceof Name) {
if ($replacement = $this->findAlias($subNode)) {
$node->$name = $replacement;
}
}
return $node;
}
return $node;
}
/**
@@ -118,8 +132,8 @@ class UseStatementPass extends CodeCleanerPass
foreach ($this->aliases as $alias => $prefix) {
if ($that === $alias) {
return new FullyQualifiedName($prefix->toString());
} elseif (\substr($that, 0, \strlen($alias) + 1) === $alias . '\\') {
return new FullyQualifiedName($prefix->toString() . \substr($name, \strlen($alias)));
} elseif (\substr($that, 0, \strlen($alias) + 1) === $alias.'\\') {
return new FullyQualifiedName($prefix->toString().\substr($name, \strlen($alias)));
}
}
}

View File

@@ -3,7 +3,7 @@
/*
* This file is part of Psy Shell.
*
* (c) 2012-2018 Justin Hileman
* (c) 2012-2022 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -13,9 +13,7 @@ namespace Psy\CodeCleaner;
use PhpParser\Node;
use PhpParser\Node\Expr;
use PhpParser\Node\Expr\ClassConstFetch;
use PhpParser\Node\Expr\New_;
use PhpParser\Node\Expr\StaticCall;
use PhpParser\Node\Expr\Ternary;
use PhpParser\Node\Stmt;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\Do_;
@@ -34,17 +32,11 @@ use Psy\Exception\FatalErrorException;
*/
class ValidClassNamePass extends NamespaceAwarePass
{
const CLASS_TYPE = 'class';
const CLASS_TYPE = 'class';
const INTERFACE_TYPE = 'interface';
const TRAIT_TYPE = 'trait';
const TRAIT_TYPE = 'trait';
private $conditionalScopes = 0;
private $atLeastPhp55;
public function __construct()
{
$this->atLeastPhp55 = \version_compare(PHP_VERSION, '5.5', '>=');
}
/**
* Validate class, interface and trait definitions.
@@ -54,6 +46,8 @@ class ValidClassNamePass extends NamespaceAwarePass
* trait methods.
*
* @param Node $node
*
* @return int|Node|null Replacement node (or special return value)
*/
public function enterNode(Node $node)
{
@@ -61,51 +55,42 @@ class ValidClassNamePass extends NamespaceAwarePass
if (self::isConditional($node)) {
$this->conditionalScopes++;
} else {
// @todo add an "else" here which adds a runtime check for instances where we can't tell
// whether a class is being redefined by static analysis alone.
if ($this->conditionalScopes === 0) {
if ($node instanceof Class_) {
$this->validateClassStatement($node);
} elseif ($node instanceof Interface_) {
$this->validateInterfaceStatement($node);
} elseif ($node instanceof Trait_) {
$this->validateTraitStatement($node);
}
return;
}
if ($this->conditionalScopes === 0) {
if ($node instanceof Class_) {
$this->validateClassStatement($node);
} elseif ($node instanceof Interface_) {
$this->validateInterfaceStatement($node);
} elseif ($node instanceof Trait_) {
$this->validateTraitStatement($node);
}
}
}
/**
* Validate `new` expressions, class constant fetches, and static calls.
*
* @throws FatalErrorException if a class, interface or trait is referenced which does not exist
* @throws FatalErrorException if a class extends something that is not a class
* @throws FatalErrorException if a class implements something that is not an interface
* @throws FatalErrorException if an interface extends something that is not an interface
* @throws FatalErrorException if a class, interface or trait redefines an existing class, interface or trait name
*
* @param Node $node
*
* @return int|Node|Node[]|null Replacement node (or special return value)
*/
public function leaveNode(Node $node)
{
if (self::isConditional($node)) {
$this->conditionalScopes--;
} elseif ($node instanceof New_) {
$this->validateNewExpression($node);
} elseif ($node instanceof ClassConstFetch) {
$this->validateClassConstFetchExpression($node);
} elseif ($node instanceof StaticCall) {
$this->validateStaticCallExpression($node);
return;
}
}
private static function isConditional(Node $node)
private static function isConditional(Node $node): bool
{
return $node instanceof If_ ||
$node instanceof While_ ||
$node instanceof Do_ ||
$node instanceof Switch_;
$node instanceof Switch_ ||
$node instanceof Ternary;
}
/**
@@ -143,50 +128,6 @@ class ValidClassNamePass extends NamespaceAwarePass
$this->ensureCanDefine($stmt, self::TRAIT_TYPE);
}
/**
* Validate a `new` expression.
*
* @param New_ $stmt
*/
protected function validateNewExpression(New_ $stmt)
{
// if class name is an expression or an anonymous class, give it a pass for now
if (!$stmt->class instanceof Expr && !$stmt->class instanceof Class_) {
$this->ensureClassExists($this->getFullyQualifiedName($stmt->class), $stmt);
}
}
/**
* Validate a class constant fetch expression's class.
*
* @param ClassConstFetch $stmt
*/
protected function validateClassConstFetchExpression(ClassConstFetch $stmt)
{
// there is no need to check exists for ::class const for php 5.5 or newer
if (\strtolower($stmt->name) === 'class' && $this->atLeastPhp55) {
return;
}
// if class name is an expression, give it a pass for now
if (!$stmt->class instanceof Expr) {
$this->ensureClassOrInterfaceExists($this->getFullyQualifiedName($stmt->class), $stmt);
}
}
/**
* Validate a class constant fetch expression's class.
*
* @param StaticCall $stmt
*/
protected function validateStaticCallExpression(StaticCall $stmt)
{
// if class name is an expression, give it a pass for now
if (!$stmt->class instanceof Expr) {
$this->ensureMethodExists($this->getFullyQualifiedName($stmt->class), $stmt->name, $stmt);
}
}
/**
* Ensure that no class, interface or trait name collides with a new definition.
*
@@ -195,8 +136,13 @@ class ValidClassNamePass extends NamespaceAwarePass
* @param Stmt $stmt
* @param string $scopeType
*/
protected function ensureCanDefine(Stmt $stmt, $scopeType = self::CLASS_TYPE)
protected function ensureCanDefine(Stmt $stmt, string $scopeType = self::CLASS_TYPE)
{
// Anonymous classes don't have a name, and uniqueness shouldn't be enforced.
if ($stmt->name === null) {
return;
}
$name = $this->getFullyQualifiedName($stmt->name);
// check for name collisions
@@ -226,7 +172,7 @@ class ValidClassNamePass extends NamespaceAwarePass
* @param string $name
* @param Stmt $stmt
*/
protected function ensureClassExists($name, $stmt)
protected function ensureClassExists(string $name, Stmt $stmt)
{
if (!$this->classExists($name)) {
throw $this->createError(\sprintf('Class \'%s\' not found', $name), $stmt);
@@ -241,7 +187,7 @@ class ValidClassNamePass extends NamespaceAwarePass
* @param string $name
* @param Stmt $stmt
*/
protected function ensureClassOrInterfaceExists($name, $stmt)
protected function ensureClassOrInterfaceExists(string $name, Stmt $stmt)
{
if (!$this->classExists($name) && !$this->interfaceExists($name)) {
throw $this->createError(\sprintf('Class \'%s\' not found', $name), $stmt);
@@ -256,7 +202,7 @@ class ValidClassNamePass extends NamespaceAwarePass
* @param string $name
* @param Stmt $stmt
*/
protected function ensureClassOrTraitExists($name, $stmt)
protected function ensureClassOrTraitExists(string $name, Stmt $stmt)
{
if (!$this->classExists($name) && !$this->traitExists($name)) {
throw $this->createError(\sprintf('Class \'%s\' not found', $name), $stmt);
@@ -272,7 +218,7 @@ class ValidClassNamePass extends NamespaceAwarePass
* @param string $name
* @param Stmt $stmt
*/
protected function ensureMethodExists($class, $name, $stmt)
protected function ensureMethodExists(string $class, string $name, Stmt $stmt)
{
$this->ensureClassOrTraitExists($class, $stmt);
@@ -304,7 +250,7 @@ class ValidClassNamePass extends NamespaceAwarePass
* @param Interface_[] $interfaces
* @param Stmt $stmt
*/
protected function ensureInterfacesExist($interfaces, $stmt)
protected function ensureInterfacesExist(array $interfaces, Stmt $stmt)
{
foreach ($interfaces as $interface) {
/** @var string $name */
@@ -321,11 +267,13 @@ class ValidClassNamePass extends NamespaceAwarePass
* @deprecated No longer used. Scope type should be passed into ensureCanDefine directly.
* @codeCoverageIgnore
*
* @throws FatalErrorException
*
* @param Stmt $stmt
*
* @return string
*/
protected function getScopeType(Stmt $stmt)
protected function getScopeType(Stmt $stmt): string
{
if ($stmt instanceof Class_) {
return self::CLASS_TYPE;
@@ -334,6 +282,8 @@ class ValidClassNamePass extends NamespaceAwarePass
} elseif ($stmt instanceof Trait_) {
return self::TRAIT_TYPE;
}
throw $this->createError('Unsupported statement type', $stmt);
}
/**
@@ -345,7 +295,7 @@ class ValidClassNamePass extends NamespaceAwarePass
*
* @return bool
*/
protected function classExists($name)
protected function classExists(string $name): bool
{
// Give `self`, `static` and `parent` a pass. This will actually let
// some errors through, since we're not checking whether the keyword is
@@ -364,7 +314,7 @@ class ValidClassNamePass extends NamespaceAwarePass
*
* @return bool
*/
protected function interfaceExists($name)
protected function interfaceExists(string $name): bool
{
return \interface_exists($name) || $this->findInScope($name) === self::INTERFACE_TYPE;
}
@@ -376,7 +326,7 @@ class ValidClassNamePass extends NamespaceAwarePass
*
* @return bool
*/
protected function traitExists($name)
protected function traitExists(string $name): bool
{
return \trait_exists($name) || $this->findInScope($name) === self::TRAIT_TYPE;
}
@@ -388,7 +338,7 @@ class ValidClassNamePass extends NamespaceAwarePass
*
* @return string|null
*/
protected function findInScope($name)
protected function findInScope(string $name)
{
$name = \strtolower($name);
if (isset($this->currentScope[$name])) {
@@ -404,8 +354,8 @@ class ValidClassNamePass extends NamespaceAwarePass
*
* @return FatalErrorException
*/
protected function createError($msg, $stmt)
protected function createError(string $msg, Stmt $stmt): FatalErrorException
{
return new FatalErrorException($msg, 0, E_ERROR, null, $stmt->getLine());
return new FatalErrorException($msg, 0, \E_ERROR, null, $stmt->getLine());
}
}

View File

@@ -1,90 +0,0 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2018 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\CodeCleaner;
use PhpParser\Node;
use PhpParser\Node\Expr;
use PhpParser\Node\Expr\ClassConstFetch;
use PhpParser\Node\Expr\ConstFetch;
use PhpParser\Node\Identifier;
use Psy\Exception\FatalErrorException;
/**
* Validate that namespaced constant references will succeed.
*
* This pass throws a FatalErrorException rather than letting PHP run
* headfirst into a real fatal error and die.
*
* @todo Detect constants defined in the current code snippet?
* ... Might not be worth it, since it would need to both be defining and
* referencing a namespaced constant, which doesn't seem like that big of
* a target for failure
*/
class ValidConstantPass extends NamespaceAwarePass
{
/**
* Validate that namespaced constant references will succeed.
*
* Note that this does not (yet) detect constants defined in the current code
* snippet. It won't happen very often, so we'll punt for now.
*
* @throws FatalErrorException if a constant reference is not defined
*
* @param Node $node
*/
public function leaveNode(Node $node)
{
if ($node instanceof ConstFetch && \count($node->name->parts) > 1) {
$name = $this->getFullyQualifiedName($node->name);
if (!\defined($name)) {
$msg = \sprintf('Undefined constant %s', $name);
throw new FatalErrorException($msg, 0, E_ERROR, null, $node->getLine());
}
} elseif ($node instanceof ClassConstFetch) {
$this->validateClassConstFetchExpression($node);
}
}
/**
* Validate a class constant fetch expression.
*
* @throws FatalErrorException if a class constant is not defined
*
* @param ClassConstFetch $stmt
*/
protected function validateClassConstFetchExpression(ClassConstFetch $stmt)
{
// For PHP Parser 4.x
$constName = $stmt->name instanceof Identifier ? $stmt->name->toString() : $stmt->name;
// give the `class` pseudo-constant a pass
if ($constName === 'class') {
return;
}
// if class name is an expression, give it a pass for now
if (!$stmt->class instanceof Expr) {
$className = $this->getFullyQualifiedName($stmt->class);
// if the class doesn't exist, don't throw an exception… it might be
// defined in the same line it's used or something stupid like that.
if (\class_exists($className) || \interface_exists($className)) {
$refl = new \ReflectionClass($className);
if (!$refl->hasConstant($constName)) {
$constType = \class_exists($className) ? 'Class' : 'Interface';
$msg = \sprintf('%s constant \'%s::%s\' not found', $constType, $className, $constName);
throw new FatalErrorException($msg, 0, E_ERROR, null, $stmt->getLine());
}
}
}
}
}

View File

@@ -3,7 +3,7 @@
/*
* This file is part of Psy Shell.
*
* (c) 2012-2018 Justin Hileman
* (c) 2012-2022 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -35,6 +35,9 @@ class ValidConstructorPass extends CodeCleanerPass
{
private $namespace;
/**
* @return Node[]|null Array of nodes
*/
public function beforeTraverse(array $nodes)
{
$this->namespace = [];
@@ -47,6 +50,8 @@ class ValidConstructorPass extends CodeCleanerPass
* @throws FatalErrorException the constructor function has a return type
*
* @param Node $node
*
* @return int|Node|null Replacement node (or special return value)
*/
public function enterNode(Node $node)
{
@@ -94,7 +99,7 @@ class ValidConstructorPass extends CodeCleanerPass
\implode('\\', \array_merge($this->namespace, (array) $className)),
$constructor->name
);
throw new FatalErrorException($msg, 0, E_ERROR, null, $classNode->getLine());
throw new FatalErrorException($msg, 0, \E_ERROR, null, $classNode->getLine());
}
if (\method_exists($constructor, 'getReturnType') && $constructor->getReturnType()) {
@@ -106,7 +111,7 @@ class ValidConstructorPass extends CodeCleanerPass
\implode('\\', \array_merge($this->namespace, (array) $className)),
$constructor->name
);
throw new FatalErrorException($msg, 0, E_ERROR, null, $classNode->getLine());
throw new FatalErrorException($msg, 0, \E_ERROR, null, $classNode->getLine());
}
}
}

View File

@@ -3,7 +3,7 @@
/*
* This file is part of Psy Shell.
*
* (c) 2012-2018 Justin Hileman
* (c) 2012-2022 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -12,9 +12,6 @@
namespace Psy\CodeCleaner;
use PhpParser\Node;
use PhpParser\Node\Expr;
use PhpParser\Node\Expr\FuncCall;
use PhpParser\Node\Expr\Variable;
use PhpParser\Node\Stmt\Do_;
use PhpParser\Node\Stmt\Function_;
use PhpParser\Node\Stmt\If_;
@@ -35,7 +32,11 @@ class ValidFunctionNamePass extends NamespaceAwarePass
/**
* Store newly defined function names on the way in, to allow recursion.
*
* @throws FatalErrorException if a function is redefined in a non-conditional scope
*
* @param Node $node
*
* @return int|Node|null Replacement node (or special return value)
*/
public function enterNode(Node $node)
{
@@ -52,7 +53,7 @@ class ValidFunctionNamePass extends NamespaceAwarePass
if (\function_exists($name) ||
isset($this->currentScope[\strtolower($name)])) {
$msg = \sprintf('Cannot redeclare %s()', $name);
throw new FatalErrorException($msg, 0, E_ERROR, null, $node->getLine());
throw new FatalErrorException($msg, 0, \E_ERROR, null, $node->getLine());
}
}
@@ -61,29 +62,14 @@ class ValidFunctionNamePass extends NamespaceAwarePass
}
/**
* Validate that function calls will succeed.
*
* @throws FatalErrorException if a function is redefined
* @throws FatalErrorException if the function name is a string (not an expression) and is not defined
*
* @param Node $node
*
* @return int|Node|Node[]|null Replacement node (or special return value)
*/
public function leaveNode(Node $node)
{
if (self::isConditional($node)) {
$this->conditionalScopes--;
} elseif ($node instanceof FuncCall) {
// if function name is an expression or a variable, give it a pass for now.
$name = $node->name;
if (!$name instanceof Expr && !$name instanceof Variable) {
$shortName = \implode('\\', $name->parts);
$fullName = $this->getFullyQualifiedName($name);
$inScope = isset($this->currentScope[\strtolower($fullName)]);
if (!$inScope && !\function_exists($shortName) && !\function_exists($fullName)) {
$message = \sprintf('Call to undefined function %s()', $name);
throw new FatalErrorException($message, 0, E_ERROR, null, $node->getLine());
}
}
}
}