Вход Регистрация
Файл: vendor/psy/psysh/src/CodeCleaner/ValidClassNamePass.php
Строк: 447
<?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 PsyCodeCleaner;

use 
PhpParserNode;
use 
PhpParserNodeExpr;
use 
PhpParserNodeExprTernary;
use 
PhpParserNodeStmt;
use 
PhpParserNodeStmtClass_;
use 
PhpParserNodeStmtDo_;
use 
PhpParserNodeStmtIf_;
use 
PhpParserNodeStmtInterface_;
use 
PhpParserNodeStmtSwitch_;
use 
PhpParserNodeStmtTrait_;
use 
PhpParserNodeStmtWhile_;
use 
PsyExceptionFatalErrorException;

/**
 * Validate that classes exist.
 *
 * This pass throws a FatalErrorException rather than letting PHP run
 * headfirst into a real fatal error and die.
 */
class ValidClassNamePass extends NamespaceAwarePass
{
    const 
CLASS_TYPE 'class';
    const 
INTERFACE_TYPE 'interface';
    const 
TRAIT_TYPE 'trait';

    private 
$conditionalScopes 0;

    
/**
     * Validate class, interface and trait definitions.
     *
     * Validate them upon entering the node, so that we know about their
     * presence and can validate constant fetches and static calls in class or
     * trait methods.
     *
     * @param Node $node
     *
     * @return int|Node|null Replacement node (or special return value)
     */
    
public function enterNode(Node $node)
    {
        
parent::enterNode($node);

        if (
self::isConditional($node)) {
            
$this->conditionalScopes++;

            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);
            }
        }
    }

    
/**
     * @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--;

            return;
        }
    }

    private static function 
isConditional(Node $node): bool
    
{
        return 
$node instanceof If_ ||
            
$node instanceof While_ ||
            
$node instanceof Do_ ||
            
$node instanceof Switch_ ||
            
$node instanceof Ternary;
    }

    
/**
     * Validate a class definition statement.
     *
     * @param Class_ $stmt
     */
    
protected function validateClassStatement(Class_ $stmt)
    {
        
$this->ensureCanDefine($stmtself::CLASS_TYPE);
        if (isset(
$stmt->extends)) {
            
$this->ensureClassExists($this->getFullyQualifiedName($stmt->extends), $stmt);
        }
        
$this->ensureInterfacesExist($stmt->implements$stmt);
    }

    
/**
     * Validate an interface definition statement.
     *
     * @param Interface_ $stmt
     */
    
protected function validateInterfaceStatement(Interface_ $stmt)
    {
        
$this->ensureCanDefine($stmtself::INTERFACE_TYPE);
        
$this->ensureInterfacesExist($stmt->extends$stmt);
    }

    
/**
     * Validate a trait definition statement.
     *
     * @param Trait_ $stmt
     */
    
protected function validateTraitStatement(Trait_ $stmt)
    {
        
$this->ensureCanDefine($stmtself::TRAIT_TYPE);
    }

    
/**
     * Ensure that no class, interface or trait name collides with a new definition.
     *
     * @throws FatalErrorException
     *
     * @param Stmt   $stmt
     * @param string $scopeType
     */
    
protected function ensureCanDefine(Stmt $stmtstring $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
        
$errorType null;
        if (
$this->classExists($name)) {
            
$errorType self::CLASS_TYPE;
        } elseif (
$this->interfaceExists($name)) {
            
$errorType self::INTERFACE_TYPE;
        } elseif (
$this->traitExists($name)) {
            
$errorType self::TRAIT_TYPE;
        }

        if (
$errorType !== null) {
            throw 
$this->createError(sprintf('%s named %s already exists'ucfirst($errorType), $name), $stmt);
        }

        
// Store creation for the rest of this code snippet so we can find local
        // issue too
        
$this->currentScope[strtolower($name)] = $scopeType;
    }

    
/**
     * Ensure that a referenced class exists.
     *
     * @throws FatalErrorException
     *
     * @param string $name
     * @param Stmt   $stmt
     */
    
protected function ensureClassExists(string $nameStmt $stmt)
    {
        if (!
$this->classExists($name)) {
            throw 
$this->createError(sprintf('Class '%s' not found'$name), $stmt);
        }
    }

    
/**
     * Ensure that a referenced class _or interface_ exists.
     *
     * @throws FatalErrorException
     *
     * @param string $name
     * @param Stmt   $stmt
     */
    
protected function ensureClassOrInterfaceExists(string $nameStmt $stmt)
    {
        if (!
$this->classExists($name) && !$this->interfaceExists($name)) {
            throw 
$this->createError(sprintf('Class '%s' not found'$name), $stmt);
        }
    }

    
/**
     * Ensure that a referenced class _or trait_ exists.
     *
     * @throws FatalErrorException
     *
     * @param string $name
     * @param Stmt   $stmt
     */
    
protected function ensureClassOrTraitExists(string $nameStmt $stmt)
    {
        if (!
$this->classExists($name) && !$this->traitExists($name)) {
            throw 
$this->createError(sprintf('Class '%s' not found'$name), $stmt);
        }
    }

    
/**
     * Ensure that a statically called method exists.
     *
     * @throws FatalErrorException
     *
     * @param string $class
     * @param string $name
     * @param Stmt   $stmt
     */
    
protected function ensureMethodExists(string $classstring $nameStmt $stmt)
    {
        
$this->ensureClassOrTraitExists($class$stmt);

        
// let's pretend all calls to self, parent and static are valid
        
if (in_array(strtolower($class), ['self''parent''static'])) {
            return;
        }

        
// ... and all calls to classes defined right now
        
if ($this->findInScope($class) === self::CLASS_TYPE) {
            return;
        }

        
// if method name is an expression, give it a pass for now
        
if ($name instanceof Expr) {
            return;
        }

        if (!
method_exists($class$name) && !method_exists($class'__callStatic')) {
            throw 
$this->createError(sprintf('Call to undefined method %s::%s()'$class$name), $stmt);
        }
    }

    
/**
     * Ensure that a referenced interface exists.
     *
     * @throws FatalErrorException
     *
     * @param Interface_[] $interfaces
     * @param Stmt         $stmt
     */
    
protected function ensureInterfacesExist(array $interfacesStmt $stmt)
    {
        foreach (
$interfaces as $interface) {
            
/** @var string $name */
            
$name $this->getFullyQualifiedName($interface);
            if (!
$this->interfaceExists($name)) {
                throw 
$this->createError(sprintf('Interface '%s' not found'$name), $stmt);
            }
        }
    }

    
/**
     * Get a symbol type key for storing in the scope name cache.
     *
     * @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): string
    
{
        if (
$stmt instanceof Class_) {
            return 
self::CLASS_TYPE;
        } elseif (
$stmt instanceof Interface_) {
            return 
self::INTERFACE_TYPE;
        } elseif (
$stmt instanceof Trait_) {
            return 
self::TRAIT_TYPE;
        }

        throw 
$this->createError('Unsupported statement type'$stmt);
    }

    
/**
     * Check whether a class exists, or has been defined in the current code snippet.
     *
     * Gives `self`, `static` and `parent` a free pass.
     *
     * @param string $name
     *
     * @return bool
     */
    
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
        // being used in a class scope.
        
if (in_array(strtolower($name), ['self''static''parent'])) {
            return 
true;
        }

        return 
class_exists($name) || $this->findInScope($name) === self::CLASS_TYPE;
    }

    
/**
     * Check whether an interface exists, or has been defined in the current code snippet.
     *
     * @param string $name
     *
     * @return bool
     */
    
protected function interfaceExists(string $name): bool
    
{
        return 
interface_exists($name) || $this->findInScope($name) === self::INTERFACE_TYPE;
    }

    
/**
     * Check whether a trait exists, or has been defined in the current code snippet.
     *
     * @param string $name
     *
     * @return bool
     */
    
protected function traitExists(string $name): bool
    
{
        return 
trait_exists($name) || $this->findInScope($name) === self::TRAIT_TYPE;
    }

    
/**
     * Find a symbol in the current code snippet scope.
     *
     * @param string $name
     *
     * @return string|null
     */
    
protected function findInScope(string $name)
    {
        
$name strtolower($name);
        if (isset(
$this->currentScope[$name])) {
            return 
$this->currentScope[$name];
        }
    }

    
/**
     * Error creation factory.
     *
     * @param string $msg
     * @param Stmt   $stmt
     *
     * @return FatalErrorException
     */
    
protected function createError(string $msgStmt $stmt): FatalErrorException
    
{
        return new 
FatalErrorException($msg0E_ERRORnull$stmt->getLine());
    }
}
Онлайн: 0
Реклама