<?php
namespace wapmorgan\PhpCodeFixer;

use Exception;

class Application
{
    protected $args;

    /** @var Report[] */
    protected $reports;

    /** @var boolean */
    protected $hasIssue;

    /**
     * @var PhpCodeFixer
     */
    protected $analyzer;

    /**
     * Application constructor.
     * @param $args
     */
    public function __construct($args)
    {
        $this->args = $args;
        $this->analyzer = new PhpCodeFixer();
    }

    /**
     * Runs console application
     */
    public function run()
    {
        try {
            $this->setTarget();
            $this->setMaxSize();
            $this->setExcludeList();
            $this->setSkipChecks();
            $this->setFileExtensions();

            $this->scanFiles();
            $this->printReport();
            $this->printMemoryUsage();
            if ($this->hasIssue)
                exit(1);
        } catch (Exception $e) {
            $this->exitWithError($e->getMessage(), 128);
        }
    }

    /**
     * Checks --target argument
     * @throws Exception
     */
    public function setTarget()
    {
        if (empty($this->args['--target'])) {
            $this->analyzer->setTargetPhpVersion(PhpCodeFixer::getLatestSupportedTargetVersion());
        } else {
            $this->analyzer->setTargetPhpVersion($this->args['--target']);
        }
    }

    /**
     * Checks --max-size argument
     */
    public function setMaxSize()
    {
        static $size_units = ['kb', 'mb', 'gb'];
        if (!empty($this->args['--max-size'])) {
            foreach ($size_units as $unit) {
                if (stripos($this->args['--max-size'], $unit) > 0) {
                    $max_size_value = (int)stristr($this->args['--max-size'], $unit, true);
                    $max_size = $max_size_value * pow(1024, array_search($unit, $size_units) + 1);
                    break;
                }
            }
            if (!isset($max_size)) {
                if ((int)$this->args['--max-size'] > 0)
                    $max_size = (int)$this->args['--max-size'];
            }

            if (isset($max_size)) {
                $this->analyzer->setFileSizeLimit($max_size);
                $this->echoInfoLine('Max file size set to: ' . $this->formatSize('%.3F Ui', $max_size));
            }
        }
    }

    /**
     * Checks --exclude argument
     */
    protected function setExcludeList()
    {
        if (!empty($this->args['--exclude'])) {
            $this->analyzer->setExcludedFilesList($excluded = array_map(
                function ($dir) { return trim(strtolower($dir), '/\\ '); },
                explode(',', $this->args['--exclude'])));
            $this->echoInfoLine('Excluding the following files / directories: '.implode(', ', $excluded));
        }
    }

    /**
     * Checks --exclude- argument
     */
    protected function setSkipChecks()
    {
        if (!empty($this->args['--skip-checks'])) {
            $this->analyzer->setSkippedChecks($skipped_checks = array_map('strtolower', explode(',', $this->args['--skip-checks'])));
            $this->echoInfoLine('Skipping checks containing any of the following values: '.implode(', ', $skipped_checks));
        }
    }

    /**
     * Checks --file-extensions argument
     */
    protected function setFileExtensions()
    {
        if (!empty($this->args['--file-extensions'])) {
            $exts = array_map('strtolower', array_map('trim', explode(',', $this->args['--file-extensions'])));
            if (!empty($exts)) {
                $this->analyzer->setFileExtensions($exts);
                $this->echoInfoLine('File extensions set to: '.implode(', ', $exts));
            }
        }
    }

    /**
     * Runs analyzer
     */
    protected function scanFiles()
    {
        $this->analyzer->initialize();
        $this->reports = [];
        foreach ($this->args['FILES'] as $file) {
            if (is_dir($file)) {
                TerminalInfo::echoWithColor('Scanning '.$file.' ...'.PHP_EOL, TerminalInfo::GRAY_TEXT);
                $this->reports[] = $this->analyzer->checkDir(rtrim(realpath($file), DIRECTORY_SEPARATOR));
            } else if (is_file($file)) {
                $report = new Report('File '.basename($file), dirname(realpath($file)));
                $report = $this->analyzer->checkFile(realpath($file), $report);
                if($report instanceof Report) {
                    $this->reports[] = $report;
                }
            }
        }
    }

    /**
     * Prints analyzer report
     */
    protected function printReport()
    {
        // adjustable output width
        if (TerminalInfo::isInteractive()) {
            $width = TerminalInfo::getWidth();
        } else {
            $width = 80;
        }

        $current_php = substr(PHP_VERSION, 0, 3);

        $variable_length = max(30, floor(($width - 31) * 0.4));
        $this->hasIssue = false;

        if (!empty($this->reports)) {
            $total_issues = 0;
            $replace_suggestions = $notes = [];

            foreach ($this->reports as $report) {
                echo PHP_EOL;
                TerminalInfo::echoWithColor($report->getTitle().PHP_EOL, TerminalInfo::WHITE_TEXT);

                $report = $report->getIssues();
                if (!empty($report)) {
                    echo sprintf(' %3s | %-' . $variable_length . 's | %16s | %s', 'PHP', 'File:Line', 'Type', 'Issue') . PHP_EOL;
                    $versions = array_keys($report);
                    sort($versions);

                    // print issues by version
                    foreach ($versions as $version) {
                        $issues = $report[$version];

                        // iterate issues
                        foreach ($issues as $issue) {
                            $this->hasIssue = true;
                            $total_issues++;
                            switch ($issue[0]) {
                                case 'function':
                                case 'function_usage':
                                    $color = TerminalInfo::YELLOW_TEXT;
                                    break;

                                case 'variable':
                                    $color = TerminalInfo::RED_TEXT;
                                    break;

                                case 'ini':
                                    $color = TerminalInfo::GREEN_TEXT;
                                    break;

                                case 'identifier':
                                    $color = TerminalInfo::BLUE_TEXT;
                                    break;

                                case 'constant':
                                    $color = TerminalInfo::GRAY_TEXT;
                                    break;

                                default:
                                    $color = TerminalInfo::YELLOW_TEXT;
                                    break;
                            }

                            $line_length = strlen($issue[4]);

                            echo sprintf(' %3s | %-' . ($variable_length + (TerminalInfo::isColorsCapable() ? 22 : 0)) . 's | %-16s | %s',
                                    strcmp($current_php, $version) >= 0 ? TerminalInfo::colorize($version, TerminalInfo::RED_BACKGROUND) : $version,
                                    TerminalInfo::colorize($this->normalizeAndTruncatePath($issue[3], $variable_length - $line_length - 1), TerminalInfo::WHITE_TEXT)
                                        .':'.TerminalInfo::colorize($issue[4], TerminalInfo::GRAY_TEXT),
                                    $issue[0],
                                    str_replace('_', ' ', ucfirst($issue[0])) . ' ' . TerminalInfo::colorize($issue[1].($issue[0] == 'function' ? '()' : null), $color)
                                    . ' is '
                                    . ($issue[0] == 'identifier' ? 'reserved by PHP core' : 'deprecated') . '. ') . PHP_EOL;

                            if (!empty($issue[2])) {
                                if ($issue[0] === 'function_usage') {
                                    $notes[$issue[0]][$issue[1]] = $issue[2];
                                }
                                else
                                    $replace_suggestions[$issue[0]][$issue[1]] = $issue[2];
                            }
                        }
                    }
                }
            }

            echo PHP_EOL;
            if ($total_issues > 0)
                TerminalInfo::echoWithColor(TerminalInfo::colorize('Total problems: '.$total_issues, TerminalInfo::RED_BACKGROUND).PHP_EOL, TerminalInfo::WHITE_TEXT);
            else
                TerminalInfo::echoWithColor(TerminalInfo::colorize('Analyzer has not detected any problems in your code.', TerminalInfo::GREEN_BACKGROUND).PHP_EOL, TerminalInfo::WHITE_TEXT);

            if (!empty($replace_suggestions)) {
                echo PHP_EOL;
                TerminalInfo::echoWithColor('Replace Suggestions:'.PHP_EOL, TerminalInfo::WHITE_TEXT);
                $i = 1;
                foreach ($replace_suggestions as $type => $suggestion) {
                    foreach ($suggestion as $issue => $replacement) {
                        echo ($i++).'. Don\'t use '.$type.' '
                            .TerminalInfo::colorize($issue.($type === 'function' ? '()' : null), TerminalInfo::RED_UNDERLINED_TEXT)
                            .' => Consider replace to '.TerminalInfo::colorize($replacement.($type === 'function' ? '()' : null), TerminalInfo::GREEN_TEXT).'.'.PHP_EOL;
                    }
                }
            }

            if (!empty($notes)) {
                echo PHP_EOL;
                TerminalInfo::echoWithColor('Notes:'.PHP_EOL, TerminalInfo::WHITE_TEXT);
                $i = 1;
                foreach ($notes as $type => $note) {
                    foreach ($note as $issue => $issue_note) {
                        echo ($i++).'. Usage '.TerminalInfo::colorize($issue, TerminalInfo::RED_UNDERLINED_TEXT)
                            .': '.TerminalInfo::colorize($issue_note, TerminalInfo::WHITE_TEXT).PHP_EOL;
                    }
                }
            }
        }
    }

    /**
     * Prints memory consumption
     */
    protected function printMemoryUsage()
    {
        $this->echoInfoLine('Peak memory usage: '.$this->formatSize('%.3F U', memory_get_peak_usage(), 'mb'));
    }

    /**
     * Simplifies path to fit in specific width
     * @param string $path
     * @param integer $maxLength
     * @return string
     */
    public function normalizeAndTruncatePath($path, $maxLength) {
        $truncated = 1;
        $path_parts = explode('/', str_replace('\\', '/', $path));
        $total_parts = count($path_parts);

        while (strlen($path) > $maxLength) {
            if (($truncated + 1) === $total_parts) break;
            $part_to_modify = $total_parts - 1 - $truncated;
            $chars_to_truncate = min(strlen($path_parts[$part_to_modify]) - 1, strlen($path) - $maxLength);
            if ((strlen($path) - $maxLength + 2) < strlen($path_parts[$part_to_modify]))
                $chars_to_truncate += 2;

            $path_parts[$part_to_modify] = substr($path_parts[$part_to_modify], 0, -$chars_to_truncate).'..';
            $path = implode('/', $path_parts);
            $truncated++;
        }

        return $path;
    }

    /**
     * @param string $format Sets format for size.
     * Format should containt string parsable by sprintf() function and contain one %F macro that will be replaced by size. Another macro is U/u. It will be replaced with used unit. U for uppercase, u - lowercase. If 'i' is present at the end of format string, size multiplier will be set to 1024 (and units be KiB, MiB and so on), otherwise multiplier is set to 1000.
     * @example "%.0F Ui" 617 KiB
     * @example "%.3F Ui" 617.070 KiB
     * @example "%10.3F Ui"     616.85 KiB
     * @example "%.3F U" 632.096 KB
     *
     * @param integer $bytes Size in bytes
     * @param string $unit Sets default unit. Can have these values: B, KB, MG, GB, TB, PB, EB, ZB and YB
     * @return string
     */
    public function formatSize($format, $bytes, $unit = null) {
        $units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
        $bytes = max($bytes, 0);
        $unit = strtoupper($unit);

        if (substr($format, -1) === 'i') {
            $multiplier = 1024;
            $format = substr($format, 0, -1);
        }
        else
            $multiplier = 1000;

        if ($unit === null || !in_array($unit, $units)) {
            $pow = floor(($bytes ? log($bytes) : 0) / log($multiplier));
            $pow = min($pow, count($units) - 1);

            $bytes /= pow($multiplier, $pow);
            $unit = $units[$pow];
        } else {
            $pow = array_search($unit, $units);
            $bytes /= pow($multiplier, $pow);
        }

        if ($multiplier == 1024)
            $unit = (strlen($unit) == 2) ? substr($unit, 0, 1).'iB' : $unit;
        if (strpos($format, 'u') !== false)
            $format = str_replace('u', strtolower($unit), $format);
        else
            $format = str_replace('U', $unit, $format);

        return sprintf($format, $bytes);
    }

    /**
     * Shows error and terminates script
     * @param string $message
     * @param int $code
     */
    public function exitWithError($message, $code = 128)
    {
        fwrite(STDERR, TerminalInfo::colorize($message, TerminalInfo::RED_BACKGROUND).PHP_EOL);
        exit($code);
    }

    /**
     * Prints information message
     * @param string $message
     */
    public function echoInfoLine($message)
    {
        TerminalInfo::echoWithColor($message.PHP_EOL, TerminalInfo::GRAY_TEXT);
    }
}


<?php
namespace wapmorgan\PhpCodeFixer;

use Exception;

if (!defined('T_TRAIT')) define('T_TRAIT', 'trait');

class PhpCodeFixer {

    /**
     * Version of scanner
     */
    const VERSION = '2.0.16';

    protected static $availableVersions = ['5.3', '5.4', '5.5', '5.6', '7.0', '7.1', '7.2', '7.3'];

    /**
     * @return string
     */
    public static function getLatestSupportedTargetVersion()
    {
        return static::$availableVersions[count(static::$availableVersions) - 1];
    }

    /**
     * @var integer|null Size of file to process. Can be decreased to use less memory or prevent crashes due to memory limit.
     */
    protected $fileSizeLimit;

    /**
     * @var array Extensions of file to process.
     */
    protected $fileExtensions = ['php', 'php5', 'phtml'];

    /** @var array */
    protected $excludedFilesList = [];

    /** @var array */
    protected $skippedChecks = [];

    /** @var IssuesBank */
    protected $issuesBank;

    /** @var string */
    protected $targetPhpVersion;

    /**
     * @param int|null $fileSizeLimit Maximum size of file to be scanned in bytes
     * @return PhpCodeFixer
     */
    public function setFileSizeLimit($fileSizeLimit)
    {
        $this->fileSizeLimit = $fileSizeLimit;
        return $this;
    }

    /**
     * @param array $fileExtensions
     * @return PhpCodeFixer
     */
    public function setFileExtensions($fileExtensions)
    {
        $this->fileExtensions = $fileExtensions;
        return $this;
    }

    /**
     * @param array $excludedFilesList
     * @return PhpCodeFixer
     */
    public function setExcludedFilesList($excludedFilesList)
    {
        $this->excludedFilesList = $excludedFilesList;
        return $this;
    }

    /**
     * @param array $skippedChecks
     * @return PhpCodeFixer
     */
    public function setSkippedChecks($skippedChecks)
    {
        $this->skippedChecks = $skippedChecks;
        return $this;
    }

    /**
     * @param string $targetPhpVersion
     * @return PhpCodeFixer
     * @throws Exception
     */
    public function setTargetPhpVersion($targetPhpVersion)
    {
        if (!in_array($targetPhpVersion, static::$availableVersions, true)) {
            throw new Exception('Target version is not valid. Available target version: '.implode(', ', static::$availableVersions));
        }
        $this->targetPhpVersion = $targetPhpVersion;
        return $this;
    }

    /**
     * Loads issues
     */
    public function initialize()
    {
        // init issues bank
        $this->issuesBank = new IssuesBank();
        foreach (static::$availableVersions as $version) {
            $version_issues = include dirname(dirname(__FILE__)).'/data/'.$version.'.php';

            foreach ($version_issues as $issues_type => $issues_list) {
                $this->issuesBank->import($version, $issues_type, $issues_list);
            }

            if ($version == $this->targetPhpVersion)
                break;
        }
    }

    /**
     * @param string $dir
     * @return Report
     */
    public function checkDir($dir) {
        $report = new Report('Folder '.$dir, $dir);
        $this->checkDirInternal($dir, $report);
        return $report;
    }

    /**
     * @param string $dir
     * @param Report $report
     */
    protected function checkDirInternal($dir, Report $report) {
        foreach (glob($dir.'/*') as $file) {
            if (is_dir($file)) {
                if (in_array(strtolower(basename($file)), $this->excludedFilesList, true)) {
//                    TerminalInfo::echoWithColor('Folder ' . $file . ' skipped' . PHP_EOL, TerminalInfo::GRAY_TEXT);
                } else
                    $this->checkDirInternal($file, $report);
            } else if (is_file($file) && in_array(strtolower(pathinfo($file, PATHINFO_EXTENSION)), $this->fileExtensions)) {
                $this->checkFile($file, $report);
            }
        }
    }

    /**
     * @param string $file
     * @param Report|null $report
     * @return Report|bool
     */
    public function checkFile($file, Report $report = null) {
        if ($this->fileSizeLimit !== null && filesize($file) > $this->fileSizeLimit) {
//            TerminalInfo::echoWithColor('Skipping file '.$file.' due to file size limit.'.PHP_EOL, TerminalInfo::GRAY_TEXT);
            return false;
        }
        if (empty($report)) $report = new Report('File '.basename($file), dirname(realpath($file)));
        $tokens = token_get_all(file_get_contents($file));

        // cut off heredoc, comments
        while (in_array_column($tokens, T_START_HEREDOC, 0)) {
            $start = array_search_column($tokens, T_START_HEREDOC, 0);
            $end = array_search_column($tokens, T_END_HEREDOC, 0);
            array_splice($tokens, $start, ($end - $start + 1));
        }

        // find for deprecated functions
        $deprecated_functions = self::filterSkippedChecks($this->issuesBank->getAll('functions'), $this->skippedChecks);
        $used_functions = array_filter_by_column($tokens, T_STRING, 0, true);
        foreach ($used_functions as $used_function_i => $used_function) {
            if (isset($deprecated_functions[$used_function[1]])) {
                // additional check for "(" after this token
                if (!isset($tokens[$used_function_i+1]) || $tokens[$used_function_i+1] !== '(')
                    continue;
                // additional check for lack of "->" and "::" before this token
                if (isset($tokens[$used_function_i-1])
                    && is_array($tokens[$used_function_i-1])
                    && in_array($tokens[$used_function_i-1][0], [T_OBJECT_OPERATOR, T_DOUBLE_COLON], true))
                    continue;
                // additional check for lack of "function" before this token
                if (isset($tokens[$used_function_i-2]) && is_array($tokens[$used_function_i-2]) && $tokens[$used_function_i-2][0] === T_FUNCTION)
                    continue;

                $function = $deprecated_functions[$used_function[1]];
                $report->add($function[1], 'function', $used_function[1], ($function[0] != $used_function[1] ? $function[0] : null), $file, $used_function[2]);
            }
        }

        // find for deprecated constants
        $deprecated_constants = self::filterSkippedChecks($this->issuesBank->getAll('constants'), $this->skippedChecks);
        $used_constants = array_filter_by_column($tokens, T_STRING, 0, true);
        foreach ($used_constants as $used_constant_i => $used_constant) {
            if (isset($deprecated_constants[$used_constant[1]])) {
                $constant = $deprecated_constants[$used_constant[1]];
                $report->add($constant[1], 'constant', $used_constant[1], ($constant[0] != $used_constant[1] ? $constant[0] : null), $file, $used_constant[2]);
            }
        }

        // find for deprecated ini settings
        $deprecated_ini_settings = self::filterSkippedChecks($this->issuesBank->getAll('ini_settings'), $this->skippedChecks);
        foreach ($tokens as $i => $token) {
            if ($token[0] == T_STRING && in_array($token[1], array('ini_alter', 'ini_set', 'ini_get', 'ini_restore'))) {
                // syntax structure check
                if ($tokens[$i+1] == '(' && is_array($tokens[$i+2]) && $tokens[$i+2][0] == T_CONSTANT_ENCAPSED_STRING) {
                    $ini_setting = $tokens[$i+2]; // ('ini_setting'
                    $ini_setting[1] = trim($ini_setting[1], '\'"');
                    if (isset($deprecated_ini_settings[$ini_setting[1]])) {
                        $deprecated_setting = $deprecated_ini_settings[$ini_setting[1]];
                        $report->add($deprecated_setting[1], 'ini', $ini_setting[1], ($deprecated_setting[0] != $ini_setting[1] ? $deprecated_setting[0] : null), $file, $ini_setting[2]);
                    }
                }
            }
        }

        // find for deprecated functions usage
        $deprecated_functions_usage = self::filterSkippedChecks($this->issuesBank->getAll('functions_usage'), $this->skippedChecks);

        /** @var array $global_deprecated_usage_checkers List of global checkers (for all function calls) */
        $global_deprecated_usage_checkers = [];
        foreach ($deprecated_functions_usage as $function => $function_usage_checker) {
            if (is_int($function)) {
                $global_deprecated_usage_checkers[] = $function_usage_checker;
                unset($deprecated_functions_usage[$function]);
            }
        }

        $function_declaration = false;
        $object_function_call = false;

        foreach ($tokens as $i => $token) {
            if ($token[0] == T_FUNCTION) {
                $function_declaration = true;
                continue;
            }

            if ($function_declaration === true) {
                if ($token === '{') {
                    $function_declaration = false;
                }

                continue;
            }

            // not a string: for sure not a function / method call
            if ($token[0] != T_STRING) {
                continue;
            }

            // not a function usage: method call
            // bug #36
            if ($tokens[$i-1][0] === T_OBJECT_OPERATOR) {
                $object_function_call = true;
            }

            // skip whitespaces
            // bug #36
            while ($tokens[$i+1][0] === T_WHITESPACE) {
                $i++;
            }

            // check if the next non-whitespace character is '('
            if ((!isset($tokens[$i + 1]) || $tokens[$i + 1] !== '(')) {
                continue;
            }

            if (!isset($deprecated_functions_usage[$token[1]]) && empty($global_deprecated_usage_checkers))
                continue;

            // get func arguments
            $functionTokens = [$token];
            $k = $i+2;
            $braces = 1;
            while ($braces > 0 && isset($tokens[$k])) {
                if (count($functionTokens) > 1 || $tokens[$k] !== ')') {
                    if ($tokens[$k][0] !== T_WHITESPACE)
                        $functionTokens[] = $tokens[$k];
                }
                if ($tokens[$k] === ')') {/*var_dump($tokens[$k]);*/ $braces--;}
                else if ($tokens[$k] === '(') {/*var_dump($tokens[$k]);*/ $braces++; }
                // var_dump($braces);
                $k++;
            }
            //$function[] = $tokens[$k];

            if ($object_function_call) {
                $object_function_call = false;
                continue;
            }

            // checking exactly this function usage
            if (isset($deprecated_functions_usage[$token[1]])) {
                $result = self::callFunctionUsageChecker(ltrim($deprecated_functions_usage[$token[1]][0], '@'),
                    $token[1], $functionTokens);
                if ($result) {
                    $report->add($deprecated_functions_usage[$token[1]][1],
                        'function_usage',
                        $token[1] . '() (' . $deprecated_functions_usage[$token[1]][0] . ')',
                        is_string($result) ? $result : null,
                        $file,
                        $token[2]);
                }
            }

            // checking global function usages
            if (!empty($global_deprecated_usage_checkers)) {
                foreach ($global_deprecated_usage_checkers as $global_function_usage_checker) {
                    $result = self::callFunctionUsageChecker(ltrim($global_function_usage_checker[0], '@'),
                        $token[1],
                        $functionTokens);
                    if ($result) {
                        $report->add($global_function_usage_checker[1],
                            'function_usage',
                            $token[1] . '() (' . $global_function_usage_checker[0] . ')',
                            is_string($result) ? $result : null,
                            $file,
                            $token[2]);
                    }
                }
            }
        }

        // find for deprecated variables
        $deprecated_variables = self::filterSkippedChecks($this->issuesBank->getAll('variables'), $this->skippedChecks);
        $used_variables = array_filter_by_column($tokens, T_VARIABLE, 0);
        foreach ($used_variables as $used_variable) {
            if (isset($deprecated_variables[$used_variable[1]])) {
                $variable = $deprecated_variables[$used_variable[1]];
                $report->add($variable[1], 'variable', $used_variable[1], ($variable[0] != $used_variable[1] ? $variable[0] : null), $file, $used_variable[2]);
            }
        }

        // oop reserved words
        $oop_words = [T_CLASS, T_INTERFACE];
        if (defined('T_TRAIT')) $oop_words[] = T_TRAIT;

        // find for reserved identifiers used as names
        $identifiers = self::filterSkippedChecks($this->issuesBank->getAll('identifiers'), $this->skippedChecks);
        if (!empty($identifiers)) {
            foreach ($tokens as $i => $token) {
                if (in_array($token[0], $oop_words)) {
                    if (isset($tokens[$i+2]) && is_array($tokens[$i+2]) && $tokens[$i+2][0] == T_STRING) {
                        $used_identifier = $tokens[$i+2];
                        if (isset($identifiers[$used_identifier[1]])) {
                            $identifier = $identifiers[$used_identifier[1]];
                            $report->add($identifier[1], 'identifier', $used_identifier[1], null, $file, $used_identifier[2]);
                        }
                    }
                }
            }
        }

        // find for methods naming deprecations
        $methods_naming = self::filterSkippedChecks($this->issuesBank->getAll('methods_naming'), $this->skippedChecks);
        if (!empty($methods_naming)) {
            $namespace = null;
            while (in_array_column($tokens, T_CLASS, 0)) {
                $total = count($tokens);
                $i = array_search_column($tokens, T_CLASS, 0);
                $class_start = $i;
                if (!is_array($tokens[$class_start-1]) || $tokens[$class_start-1][1] != '::' && $tokens[$class_start+1] !== '(') {
                    $namespace = self::findClassNamespaceInTokens($tokens, $class_start, $namespace);
                    $class_name = $tokens[$i+2][1];
                    $methods = [];
                    $braces = 1;
                    while($tokens[$i] !== '{') {
                        $i++;
                    }
                    $i++;
                    while (($braces > 0) && (($i+1) <= $total)) {
                        if ($tokens[$i] == '{') {
                            $braces++;
                            /*echo '++';*/
                        } else if ($tokens[$i] == '}') {
                            $braces--;
                            /*echo '--';*/
                        } else if (is_array($tokens[$i]) && $tokens[$i][0] == T_FUNCTION && is_array($tokens[$i+2])) {
                            $method_attributes = [];
                            $attributes_index = 2;
                            while (is_array($tokens[$i - $attributes_index])
                                && in_array($tokens[$i - $attributes_index][1], ['static', 'public', 'private', 'protected'], true)) {
                                $method_attributes[] = $tokens[$i - $attributes_index][1];
                                $attributes_index += 2;
                            }
                            $method_name = $tokens[$i+2][1];
                            $methods[$method_name] = [
                                'line' => $tokens[$i][2],
                                'attributes' => $method_attributes
                            ];
                        }
                        $i++;
                    }
                    foreach ($methods as $method_name => $method_data) {
                        foreach ($methods_naming as $methods_naming_checker) {
                            $checker = ltrim($methods_naming_checker[0], '@');
                            require_once dirname(dirname(__FILE__)).'/data/'.$checker.'.php';
                            $checker = __NAMESPACE__.'\\'.$checker;
                            $result = $checker($class_name, $method_name, $method_data['attributes'], $methods, $namespace);
                            if($result !== false) {
                                $report->add($methods_naming_checker[1], 'method_name', $method_name.':'.$class_name.' ('.$methods_naming_checker[0].')', null, $file, $method_data['line']);
                            }
                        }
                    }
                } else {
                    // ::class
                    $i++;
                }
                array_splice($tokens, 0, $i);
            }
        }
        return $report;
    }

    /**
     * @param array $checks
     * @param array $skipChecks
     * @return array
     */
    private static function filterSkippedChecks(array $checks, array $skipChecks) {
        $allowed_checks = array_filter(array_keys($checks), function($key) use ($skipChecks) {
            foreach($skipChecks as $skipCheck) {
                if (stripos($key, $skipCheck) !== false) {
                    return false;
                }
            }
            return true;
        });

        return array_intersect_key($checks, array_flip($allowed_checks));
    }

    /**
     * @param array $tokens
     * @param int $class_pos
     * @param string $default -- default: ''
     * @return bool|string
     */
    private static function findClassNamespaceInTokens(array $tokens, $class_pos, $default = '') {
        $namespace_tokens = array_slice($tokens, 0, $class_pos - 1);
        $namespace_pos = array_search_column($namespace_tokens, T_NAMESPACE, 0);

        if (empty($namespace_pos)) {
            return $default;
        }

        $namespace_tokens = array_slice($namespace_tokens, $namespace_pos);
        $namespace = '';

        foreach ($namespace_tokens as $token) {
            if (is_array($token) && in_array($token[0], [T_STRING, T_NS_SEPARATOR])) {
                $namespace .= $token[1];
            }
            else if (in_array($token, [';', '{'])) {
                break;
            }
        }

        return (!empty($namespace)) ? $namespace : '';
    }

    /**
     * Creates a tokens hierarchy by () from plain list
     * @param array $tokens
     * @return array
     */
    public static function makeFunctionCallTree(array $tokens) {
        $tree = [];
        $braces_level = 0;
        $i = 1;

        while (/*$braces > 0 &&*/ isset($tokens[$i])) {
            if ($tokens[$i] == '(') $braces_level++;
            else if ($tokens[$i] == ')') $braces_level--;
            else $tree[$braces_level][] = $tokens[$i];
            $i++;
        }
        return $tree;
    }

    /**
     * Divide first level of tokens hierarchy by comma
     * @param array $tokens
     * @return array
     */
    public static function divideByComma(array $tokens) {
        $delimited = [];
        $comma = 0;
        foreach ($tokens as $token) {
            if ($token == ',') $comma++;
            else $delimited[$comma][] = $token;
        }
        return $delimited;
    }

    /**
     * Removes all T_WHITESPACE tokens from tokens hierarchy
     * @param array $tokens
     * @return array
     */
    public static function trimSpaces(array $tokens) {
        $trimmed = [];
        foreach ($tokens as $token) {
            if (is_array($token)) {
                if ($token[0] == T_WHITESPACE)
                    continue;
                else
                    $trimmed[] = self::trimSpaces($token);
            }
            else
                $trimmed[] = $token;
        }
        return $trimmed;
    }

    /**
     * Calls function-usage checker
     * @param string $checker
     * @param string $functionName
     * @param array $callTokens
     * @return boolean
     */
    protected static function callFunctionUsageChecker($checker, $functionName, array $callTokens)
    {
        require_once dirname(dirname(__FILE__)).'/data/'.$checker.'.php';
        $checker = __NAMESPACE__ . '\\' . $checker;
        $result = $checker($callTokens, $functionName);
        return $result;
    }
}


<?php
namespace wapmorgan\PhpCodeFixer;

class Report {
    /** @var array */
    protected $records = [];

    /** @var string */
    protected $title;

    /** @var string */
    protected $removablePath;

    /**
     * Report constructor.
     * @param string $reportTitle Title of report
     * @param string|null $removablePath If present, this part of paths will be removed from added issues
     */
    public function __construct($reportTitle, $removablePath = null)
    {
        $this->title = $reportTitle;
        if ($removablePath !== null)
            $this->removablePath = $removablePath;
    }

    /**
     * Adds issue to the report
     * @param string $version PHP version
     * @param string $type Issue type
     * @param string $text Issue description
     * @param string|null $replacement Possible replacement
     * @param string $file File in which issue present
     * @param integer $line Line of file
     * @return void
     */
    public function add($version, $type, $text, $replacement, $file, $line) {
        if ($this->removablePath !== null && strncasecmp($file, $this->removablePath, strlen($this->removablePath)) === 0)
            $file = substr($file, strlen($this->removablePath));
        $this->records[$version][] = [$type, $text, $replacement, $file, $line];
    }

    /**
     * Returns list of all issues
     * @return array
     */
    public function getIssues() {
        return $this->records;
    }

    /**
     * Returns title of report
     * @return string
     */
    public function getTitle()
    {
        return $this->title;
    }
}


<?php
namespace wapmorgan\PhpCodeFixer;

class TerminalInfo {
    const RESET_COLOR = "\e[0m";
    const ORANGE_TEXT = "\e[0;33m";
    const YELLOW_TEXT = "\e[0;93m";
    const GRAY_TEXT = "\e[0;37m";
    const RED_TEXT = "\e[0;31m";
    const GREEN_TEXT = "\e[0;32m";
    const BLUE_TEXT = "\e[0;34m";
    const WHITE_TEXT = "\e[1;97m";

    const RED_UNDERLINED_TEXT = "\e[4;31m";
    const GREEN_UNDERLINED_TEXT = "\e[4;32m";

    const RED_BACKGROUND = "\e[41m";
    const GREEN_BACKGROUND = "\e[42m";

    /** @var array Size of terminal */
    static protected $size;

    /** @var boolean Cached value of terminal colors ability */
    static protected $colorsCapability;

    /**
     * Checks that output is a terminal, not a file|pipe
     * @return bool
     */
    static public function isInteractive() {
        if (strncasecmp(PHP_OS, 'win', 3) === 0) {
            // windows has no test for that
            return true;
        } else {
            if (function_exists('posix_isatty'))
                return posix_isatty(STDOUT);

            // test with fstat()
            $mode = fstat(STDIN);
            return ($mode['mode'] & 0170000) == 0020000; // charater flag (input iteractive)
        }
    }

    /**
     * Checks that terminal supports colors
     * @return bool
     */
    static public function isColorsCapable()
    {
        if (self::$colorsCapability === null) {
            if (!static::isUnixPlatform())
                self::$colorsCapability = false;
            else if (!static::isInteractive())
                self::$colorsCapability = false;
            else {
                $tput_presence = static::exec('which tput');
                if (strlen(trim($tput_presence[0])) === 0)
                    self::$colorsCapability = false;
                else {
                    $tput_colors = static::exec('tput colors');
                    self::$colorsCapability = (int)$tput_colors[0] > 0;
                }
            }
        }
        return self::$colorsCapability;
    }

    /**
     * Outputs text with background/foreground color
     * @param string $text
     * @param string $color
     * @param string|null $backgroundColor
     */
    static public function echoWithColor($text, $color, $backgroundColor = null) {
        if (static::isColorsCapable())
            fwrite(STDOUT, $backgroundColor . $color . $text . self::RESET_COLOR);
        else
            fwrite(STDOUT, $text);
    }

    /**
     * Added prefix for color and postfix for color reset
     * @param $text
     * @param $color
     * @return string
     */
    static public function colorize($text, $color) {
        if (static::isColorsCapable())
            return $color.$text.self::RESET_COLOR;
        else
            return $text;
    }

    /**
     * Returns width (columns) of terminal
     * @return int
     */
    static public function getWidth() {
        if (static::$size === null)
            static::getSize();

        return static::$size[1];
    }

    /**
     * Returns height (rows) of terminal
     * @return int
     */
    static public function getHeight() {
        if (static::$size === null)
            static::getSize();

        return static::$size[0];
    }

    /**
     * Returns size of terminal on Windows
     * @return array
     */
    static protected function getWindowsTerminalSize() {
        $output = self::exec('mode', $returnCode);
        if ($returnCode !== 0)
            return [25, 80];

        foreach ($output as $i => $line) {
            if (strpos($line, ' CON') !== false) {
                $sizes = [$output[$i + 2], $output[$i + 3]];
                break;
            }
        }
        if (!isset($sizes))
            return [25, 80];

        return array_map(function($val) { list(, $val) = explode(':', $val); return trim($val); }, $sizes);
    }

    /**
     * Returns size of terminal on Unix
     * @return array
     */
    static protected function getUnixTerminalSize() {
        $out = self::exec('stty size', $returnCode);
        if ($returnCode !== 0)
            return [25, 80];
        return array_map('trim', explode(' ', $out[0]));
    }

    /**
     * Executes system command and returns output
     * @param string $cmd
     * @param null|mixed &$returnCode Exit code of command will be stored in this argument
     * @return array List of output lines
     */
    static protected function exec($cmd, &$returnCode = null)
    {
        \exec($cmd, $output, $returnCode);
        return $output;
    }

    /**
     * Gets size of terminal
     */
    static protected function getSize()
    {
        if (static::isUnixPlatform()) {
            static::$size = static::getUnixTerminalSize();
        } else {
            static::$size = static::getWindowsTerminalSize();
        }
    }

    /**
     * Checks that running platform is unix
     * @return bool
     */
    static protected function isUnixPlatform()
    {
        return (strncasecmp(PHP_OS, 'win', 3) !== 0);
    }
}


<?php

declare(strict_types=1);

namespace Roave\BetterReflection\Reflection\Adapter;

use OutOfBoundsException;
use ReflectionClass as CoreReflectionClass;
use ReflectionException as CoreReflectionException;
use Roave\BetterReflection\Reflection\ReflectionClass as BetterReflectionClass;
use Roave\BetterReflection\Reflection\ReflectionClassConstant as BetterReflectionClassConstant;
use Roave\BetterReflection\Reflection\ReflectionMethod as BetterReflectionMethod;
use function array_combine;
use function array_map;
use function func_get_args;
use function func_num_args;
use function sprintf;
use function strtolower;

class ReflectionClass extends CoreReflectionClass
{
    /** @var BetterReflectionClass */
    private $betterReflectionClass;

    public function __construct(BetterReflectionClass $betterReflectionClass)
    {
        $this->betterReflectionClass = $betterReflectionClass;
    }

    /**
     * {@inheritDoc}
     */
    public static function export($argument, $return = false)
    {
        return BetterReflectionClass::export(...func_get_args());
    }

    /**
     * {@inheritDoc}
     */
    public function __toString()
    {
        return $this->betterReflectionClass->__toString();
    }

    /**
     * {@inheritDoc}
     */
    public function getName()
    {
        return $this->betterReflectionClass->getName();
    }

    /**
     * {@inheritDoc}
     */
    public function isAnonymous()
    {
        return $this->betterReflectionClass->isAnonymous();
    }

    /**
     * {@inheritDoc}
     */
    public function isInternal()
    {
        return $this->betterReflectionClass->isInternal();
    }

    /**
     * {@inheritDoc}
     */
    public function isUserDefined()
    {
        return $this->betterReflectionClass->isUserDefined();
    }

    /**
     * {@inheritDoc}
     */
    public function isInstantiable()
    {
        return $this->betterReflectionClass->isInstantiable();
    }

    /**
     * {@inheritDoc}
     */
    public function isCloneable()
    {
        return $this->betterReflectionClass->isCloneable();
    }

    /**
     * {@inheritDoc}
     */
    public function getFileName()
    {
        return $this->betterReflectionClass->getFileName() ?? false;
    }

    /**
     * {@inheritDoc}
     */
    public function getStartLine()
    {
        return $this->betterReflectionClass->getStartLine();
    }

    /**
     * {@inheritDoc}
     */
    public function getEndLine()
    {
        return $this->betterReflectionClass->getEndLine();
    }

    /**
     * {@inheritDoc}
     */
    public function getDocComment()
    {
        return $this->betterReflectionClass->getDocComment() ?: false;
    }

    /**
     * {@inheritDoc}
     */
    public function getConstructor()
    {
        try {
            return new ReflectionMethod($this->betterReflectionClass->getConstructor());
        } catch (OutOfBoundsException $e) {
            return null;
        }
    }

    /**
     * {@inheritDoc}
     */
    public function hasMethod($name)
    {
        return $this->betterReflectionClass->hasMethod($this->getMethodRealName($name));
    }

    /**
     * {@inheritDoc}
     */
    public function getMethod($name)
    {
        return new ReflectionMethod($this->betterReflectionClass->getMethod($this->getMethodRealName($name)));
    }

    private function getMethodRealName(string $name) : string
    {
        $realMethodNames = array_map(static function (BetterReflectionMethod $method) : string {
            return $method->getName();
        }, $this->betterReflectionClass->getMethods());

        $methodNames = array_combine(array_map(static function (string $methodName) : string {
            return strtolower($methodName);
        }, $realMethodNames), $realMethodNames);

        return $methodNames[strtolower($name)] ?? $name;
    }

    /**
     * {@inheritDoc}
     */
    public function getMethods($filter = null)
    {
        $methods = $this->betterReflectionClass->getMethods();

        $wrappedMethods = [];
        foreach ($methods as $key => $method) {
            $wrappedMethods[$key] = new ReflectionMethod($method);
        }

        return $wrappedMethods;
    }

    /**
     * {@inheritDoc}
     */
    public function hasProperty($name)
    {
        return $this->betterReflectionClass->hasProperty($name);
    }

    /**
     * {@inheritDoc}
     */
    public function getProperty($name)
    {
        $betterReflectionProperty = $this->betterReflectionClass->getProperty($name);

        if ($betterReflectionProperty === null) {
            throw new CoreReflectionException(sprintf('Property "%s" does not exist', $name));
        }

        return new ReflectionProperty($betterReflectionProperty);
    }

    /**
     * {@inheritDoc}
     */
    public function getProperties($filter = null)
    {
        $properties = $this->betterReflectionClass->getProperties();

        $wrappedProperties = [];
        foreach ($properties as $key => $property) {
            $wrappedProperties[$key] = new ReflectionProperty($property);
        }

        return $wrappedProperties;
    }

    /**
     * {@inheritDoc}
     */
    public function hasConstant($name)
    {
        return $this->betterReflectionClass->hasConstant($name);
    }

    /**
     * {@inheritDoc}
     */
    public function getConstants()
    {
        return $this->betterReflectionClass->getConstants();
    }

    /**
     * {@inheritDoc}
     */
    public function getConstant($name)
    {
        return $this->betterReflectionClass->getConstant($name);
    }

    /**
     * {@inheritdoc}
     */
    public function getReflectionConstant($name)
    {
        return new ReflectionClassConstant(
            $this->betterReflectionClass->getReflectionConstant($name)
        );
    }

    /**
     * {@inheritdoc}
     */
    public function getReflectionConstants()
    {
        return array_map(static function (BetterReflectionClassConstant $betterConstant) : ReflectionClassConstant {
            return new ReflectionClassConstant($betterConstant);
        }, $this->betterReflectionClass->getReflectionConstants());
    }

    /**
     * {@inheritDoc}
     */
    public function getInterfaces()
    {
        $interfaces = $this->betterReflectionClass->getInterfaces();

        $wrappedInterfaces = [];
        foreach ($interfaces as $key => $interface) {
            $wrappedInterfaces[$key] = new self($interface);
        }

        return $wrappedInterfaces;
    }

    /**
     * {@inheritDoc}
     */
    public function getInterfaceNames()
    {
        return $this->betterReflectionClass->getInterfaceNames();
    }

    /**
     * {@inheritDoc}
     */
    public function isInterface()
    {
        return $this->betterReflectionClass->isInterface();
    }

    /**
     * {@inheritDoc}
     */
    public function getTraits()
    {
        $traits = $this->betterReflectionClass->getTraits();

        $wrappedTraits = [];
        foreach ($traits as $key => $trait) {
            $wrappedTraits[$key] = new self($trait);
        }

        return $wrappedTraits;
    }

    /**
     * {@inheritDoc}
     */
    public function getTraitNames()
    {
        return $this->betterReflectionClass->getTraitNames();
    }

    /**
     * {@inheritDoc}
     */
    public function getTraitAliases()
    {
        return $this->betterReflectionClass->getTraitAliases();
    }

    /**
     * {@inheritDoc}
     */
    public function isTrait()
    {
        return $this->betterReflectionClass->isTrait();
    }

    /**
     * {@inheritDoc}
     */
    public function isAbstract()
    {
        return $this->betterReflectionClass->isAbstract();
    }

    /**
     * {@inheritDoc}
     */
    public function isFinal()
    {
        return $this->betterReflectionClass->isFinal();
    }

    /**
     * {@inheritDoc}
     */
    public function getModifiers()
    {
        return $this->betterReflectionClass->getModifiers();
    }

    /**
     * {@inheritDoc}
     */
    public function isInstance($object)
    {
        return $this->betterReflectionClass->isInstance($object);
    }

    /**
     * {@inheritDoc}
     */
    public function newInstance($arg = null, ...$args)
    {
        throw new Exception\NotImplemented('Not implemented');
    }

    /**
     * {@inheritDoc}
     */
    public function newInstanceWithoutConstructor()
    {
        throw new Exception\NotImplemented('Not implemented');
    }

    /**
     * {@inheritDoc}
     */
    public function newInstanceArgs(?array $args = null)
    {
        throw new Exception\NotImplemented('Not implemented');
    }

    /**
     * {@inheritDoc}
     */
    public function getParentClass()
    {
        $parentClass = $this->betterReflectionClass->getParentClass();

        if ($parentClass === null) {
            return false;
        }

        return new self($parentClass);
    }

    /**
     * {@inheritDoc}
     */
    public function isSubclassOf($class)
    {
        $realParentClassNames = $this->betterReflectionClass->getParentClassNames();

        $parentClassNames = array_combine(array_map(static function (string $parentClassName) : string {
            return strtolower($parentClassName);
        }, $realParentClassNames), $realParentClassNames);

        $realParentClassName = $parentClassNames[strtolower($class)] ?? $class;

        return $this->betterReflectionClass->isSubclassOf($realParentClassName) || $this->implementsInterface($class);
    }

    /**
     * {@inheritDoc}
     */
    public function getStaticProperties()
    {
        return $this->betterReflectionClass->getStaticProperties();
    }

    /**
     * {@inheritDoc}
     */
    public function getStaticPropertyValue($name, $default = null)
    {
        $betterReflectionProperty = $this->betterReflectionClass->getProperty($name);

        if ($betterReflectionProperty === null) {
            if (func_num_args() === 2) {
                return $default;
            }

            throw new CoreReflectionException(sprintf('Property "%s" does not exist', $name));
        }

        $property = new ReflectionProperty($betterReflectionProperty);

        if (! $property->isAccessible()) {
            throw new CoreReflectionException(sprintf('Property "%s" is not accessible', $name));
        }

        if (! $property->isStatic()) {
            throw new CoreReflectionException(sprintf('Property "%s" is not static', $name));
        }

        return $property->getValue();
    }

    /**
     * {@inheritDoc}
     */
    public function setStaticPropertyValue($name, $value)
    {
        $betterReflectionProperty = $this->betterReflectionClass->getProperty($name);

        if ($betterReflectionProperty === null) {
            throw new CoreReflectionException(sprintf('Property "%s" does not exist', $name));
        }

        $property = new ReflectionProperty($betterReflectionProperty);

        if (! $property->isAccessible()) {
            throw new CoreReflectionException(sprintf('Property "%s" is not accessible', $name));
        }

        if (! $property->isStatic()) {
            throw new CoreReflectionException(sprintf('Property "%s" is not static', $name));
        }

        $property->setValue($value);
    }

    /**
     * {@inheritDoc}
     */
    public function getDefaultProperties()
    {
        return $this->betterReflectionClass->getDefaultProperties();
    }

    /**
     * {@inheritDoc}
     */
    public function isIterateable()
    {
        return $this->betterReflectionClass->isIterateable();
    }

    /**
     * {@inheritDoc}
     */
    public function implementsInterface($interface)
    {
        $realInterfaceNames = $this->betterReflectionClass->getInterfaceNames();

        $interfaceNames = array_combine(array_map(static function (string $interfaceName) : string {
            return strtolower($interfaceName);
        }, $realInterfaceNames), $realInterfaceNames);

        $realInterfaceName = $interfaceNames[strtolower($interface)] ?? $interface;

        return $this->betterReflectionClass->implementsInterface($realInterfaceName);
    }

    /**
     * {@inheritDoc}
     */
    public function getExtension()
    {
        throw new Exception\NotImplemented('Not implemented');
    }

    /**
     * {@inheritDoc}
     */
    public function getExtensionName()
    {
        return $this->betterReflectionClass->getExtensionName() ?? false;
    }

    /**
     * {@inheritDoc}
     */
    public function inNamespace()
    {
        return $this->betterReflectionClass->inNamespace();
    }

    /**
     * {@inheritDoc}
     */
    public function getNamespaceName()
    {
        return $this->betterReflectionClass->getNamespaceName();
    }

    /**
     * {@inheritDoc}
     */
    public function getShortName()
    {
        return $this->betterReflectionClass->getShortName();
    }
}


<?php

declare(strict_types=1);

namespace Roave\BetterReflection\SourceLocator\Type;

use InvalidArgumentException;
use ReflectionClass;
use ReflectionException;
use ReflectionFunction;
use Roave\BetterReflection\BetterReflection;
use Roave\BetterReflection\Identifier\Identifier;
use Roave\BetterReflection\SourceLocator\Ast\Locator as AstLocator;
use Roave\BetterReflection\SourceLocator\Exception\InvalidFileLocation;
use Roave\BetterReflection\SourceLocator\Located\LocatedSource;
use const STREAM_URL_STAT_QUIET;
use function class_exists;
use function file_exists;
use function file_get_contents;
use function function_exists;
use function interface_exists;
use function is_string;
use function restore_error_handler;
use function set_error_handler;
use function stat;
use function stream_wrapper_register;
use function stream_wrapper_restore;
use function stream_wrapper_unregister;
use function trait_exists;

/**
 * Use PHP's built in autoloader to locate a class, without actually loading.
 *
 * There are some prerequisites...
 *   - we expect the autoloader to load classes from a file (i.e. using require/include)
 */
class AutoloadSourceLocator extends AbstractSourceLocator
{
    /** @var AstLocator */
    private $astLocator;

    /**
     * Note: the constructor has been made a 0-argument constructor because `\stream_wrapper_register`
     *       is a piece of trash, and doesn't accept instances, just class names.
     */
    public function __construct(?AstLocator $astLocator = null)
    {
        $validLocator = $astLocator ?? self::$currentAstLocator ?? (new BetterReflection())->astLocator();

        parent::__construct($validLocator);

        $this->astLocator = $validLocator;
    }

    /**
     * Primarily used by the non-loading-autoloader magic trickery to determine
     * the filename used during autoloading.
     *
     * @var string|null
     */
    private static $autoloadLocatedFile;

    /** @var AstLocator */
    private static $currentAstLocator;

    /**
     * {@inheritDoc}
     *
     * @throws InvalidArgumentException
     * @throws InvalidFileLocation
     */
    protected function createLocatedSource(Identifier $identifier) : ?LocatedSource
    {
        $potentiallyLocatedFile = $this->attemptAutoloadForIdentifier($identifier);

        if (! ($potentiallyLocatedFile && file_exists($potentiallyLocatedFile))) {
            return null;
        }

        return new LocatedSource(
            file_get_contents($potentiallyLocatedFile),
            $potentiallyLocatedFile
        );
    }

    /**
     * Attempts to locate the specified identifier.
     *
     * @throws ReflectionException
     */
    private function attemptAutoloadForIdentifier(Identifier $identifier) : ?string
    {
        if ($identifier->isClass()) {
            return $this->locateClassByName($identifier->getName());
        }

        if ($identifier->isFunction()) {
            return $this->locateFunctionByName($identifier->getName());
        }

        return null;
    }

    /**
     * Attempt to locate a class by name.
     *
     * If class already exists, simply use internal reflection API to get the
     * filename and store it.
     *
     * If class does not exist, we make an assumption that whatever autoloaders
     * that are registered will be loading a file. We then override the file://
     * protocol stream wrapper to "capture" the filename we expect the class to
     * be in, and then restore it. Note that class_exists will cause an error
     * that it cannot find the file, so we squelch the errors by overriding the
     * error handler temporarily.
     *
     * @throws ReflectionException
     */
    private function locateClassByName(string $className) : ?string
    {
        if (class_exists($className, false) || interface_exists($className, false) || trait_exists($className, false)) {
            $filename = (new ReflectionClass($className))->getFileName();

            if (! is_string($filename)) {
                return null;
            }

            return $filename;
        }

        self::$autoloadLocatedFile = null;
        self::$currentAstLocator   = $this->astLocator; // passing the locator on to the implicitly instantiated `self`
        $previousErrorHandler      = set_error_handler(static function () : void {
        });
        stream_wrapper_unregister('file');
        stream_wrapper_register('file', self::class);
        class_exists($className);
        stream_wrapper_restore('file');
        set_error_handler($previousErrorHandler);

        return self::$autoloadLocatedFile;
    }

    /**
     * We can only load functions if they already exist, because PHP does not
     * have function autoloading. Therefore if it exists, we simply use the
     * internal reflection API to find the filename. If it doesn't we can do
     * nothing so throw an exception.
     *
     * @throws ReflectionException
     */
    private function locateFunctionByName(string $functionName) : ?string
    {
        if (! function_exists($functionName)) {
            return null;
        }

        $reflection         = new ReflectionFunction($functionName);
        $reflectionFileName = $reflection->getFileName();

        if (! is_string($reflectionFileName)) {
            return null;
        }

        return $reflectionFileName;
    }

    /**
     * Our wrapper simply records which file we tried to load and returns
     * boolean false indicating failure.
     *
     * @see https://php.net/manual/en/class.streamwrapper.php
     * @see https://php.net/manual/en/streamwrapper.stream-open.php
     *
     * @param string $path
     * @param string $mode
     * @param int    $options
     * @param string $opened_path
     *
     * @phpcsSuppress SlevomatCodingStandard.TypeHints.TypeHintDeclaration.MissingParameterTypeHint
     */
    public function stream_open($path, $mode, $options, &$opened_path) : bool
    {
        self::$autoloadLocatedFile = $path;

        return false;
    }

    /**
     * url_stat is triggered by calls like "file_exists". The call to "file_exists" must not be overloaded.
     * This function restores the original "file" stream, issues a call to "stat" to get the real results,
     * and then re-registers the AutoloadSourceLocator stream wrapper.
     *
     * @see https://php.net/manual/en/class.streamwrapper.php
     * @see https://php.net/manual/en/streamwrapper.url-stat.php
     *
     * @param string $path
     * @param int    $flags
     *
     * @return mixed[]|bool
     *
     * @phpcsSuppress SlevomatCodingStandard.TypeHints.TypeHintDeclaration.MissingParameterTypeHint
     */
    public function url_stat($path, $flags)
    {
        stream_wrapper_restore('file');

        if ($flags & STREAM_URL_STAT_QUIET) {
            set_error_handler(static function () {
                // Use native error handler
                return false;
            });
            $result = @stat($path);
            restore_error_handler();
        } else {
            $result = stat($path);
        }

        stream_wrapper_unregister('file');
        stream_wrapper_register('file', self::class);

        return $result;
    }
}


<?php

declare(strict_types=1);

namespace Roave\BetterReflection\SourceLocator\Type;

use Roave\BetterReflection\Identifier\Identifier;
use Roave\BetterReflection\Identifier\IdentifierType;
use Roave\BetterReflection\Reflection\Reflection;
use Roave\BetterReflection\Reflector\Exception\IdentifierNotFound;
use Roave\BetterReflection\Reflector\Reflector;
use Roave\BetterReflection\SourceLocator\Ast\Exception\ParseToAstFailure;
use Roave\BetterReflection\SourceLocator\Ast\Locator as AstLocator;
use Roave\BetterReflection\SourceLocator\Located\LocatedSource;

abstract class AbstractSourceLocator implements SourceLocator
{
    /** @var AstLocator */
    private $astLocator;

    /**
     * Children should implement this method and return a LocatedSource object
     * which contains the source and the file from which it was located.
     *
     * @example
     *   return new LocatedSource(['<?php class Foo {}', null]);
     *   return new LocatedSource([\file_get_contents('Foo.php'), 'Foo.php']);
     */
    abstract protected function createLocatedSource(Identifier $identifier) : ?LocatedSource;

    public function __construct(AstLocator $astLocator)
    {
        $this->astLocator = $astLocator;
    }

    /**
     * {@inheritDoc}
     *
     * @throws ParseToAstFailure
     */
    public function locateIdentifier(Reflector $reflector, Identifier $identifier) : ?Reflection
    {
        $locatedSource = $this->createLocatedSource($identifier);

        if (! $locatedSource) {
            return null;
        }

        try {
            return $this->astLocator->findReflection($reflector, $locatedSource, $identifier);
        } catch (IdentifierNotFound $exception) {
            return null;
        }
    }

    /**
     * {@inheritDoc}
     *
     * @throws ParseToAstFailure
     */
    final public function locateIdentifiersByType(Reflector $reflector, IdentifierType $identifierType) : array
    {
        $locatedSource = $this->createLocatedSource(new Identifier(Identifier::WILDCARD, $identifierType));

        if (! $locatedSource) {
            return [];
        }

        return $this->astLocator->findReflectionsOfType(
            $reflector,
            $locatedSource,
            $identifierType
        );
    }
}


<?php

declare(strict_types=1);

namespace Roave\BetterReflection\NodeCompiler;

use PhpParser\ConstExprEvaluator;
use PhpParser\Node;
use Roave\BetterReflection\Reflection\ReflectionClass;
use Roave\BetterReflection\Reflection\ReflectionClassConstant;
use Roave\BetterReflection\Reflector\Exception\IdentifierNotFound;
use Roave\BetterReflection\Util\FileHelper;
use function constant;
use function defined;
use function dirname;
use function realpath;
use function reset;

class CompileNodeToValue
{
    /**
     * Compile an expression from a node into a value.
     *
     * @param Node\Stmt\Expression|Node\Expr $node Node has to be processed by the PhpParser\NodeVisitor\NameResolver
     *
     * @return mixed
     *
     * @throws Exception\UnableToCompileNode
     */
    public function __invoke(Node $node, CompilerContext $context)
    {
        if ($node instanceof Node\Stmt\Expression) {
            return $this($node->expr, $context);
        }

        $constExprEvaluator = new ConstExprEvaluator(function (Node\Expr $node) use ($context) {
            if ($node instanceof Node\Expr\ConstFetch) {
                return $this->compileConstFetch($node, $context);
            }

            if ($node instanceof Node\Expr\ClassConstFetch) {
                return $this->compileClassConstFetch($node, $context);
            }

            if ($node instanceof Node\Scalar\MagicConst\Dir) {
                return $this->compileDirConstant($context);
            }

            if ($node instanceof Node\Scalar\MagicConst\Class_) {
                return $this->compileClassConstant($context);
            }

            throw Exception\UnableToCompileNode::forUnRecognizedExpressionInContext($node, $context);
        });

        return $constExprEvaluator->evaluateDirectly($node);
    }

    /**
     * Compile constant expressions
     *
     * @return bool|mixed|null
     *
     * @throws Exception\UnableToCompileNode
     */
    private function compileConstFetch(Node\Expr\ConstFetch $constNode, CompilerContext $context)
    {
        $firstName = reset($constNode->name->parts);
        switch ($firstName) {
            case 'null':
                return null;
            case 'false':
                return false;
            case 'true':
                return true;
            default:
                if (! defined($firstName)) {
                    throw Exception\UnableToCompileNode::becauseOfNotFoundConstantReference($context, $constNode);
                }

                return constant($firstName);
        }
    }

    /**
     * Compile class constants
     *
     * @return string|int|float|bool|mixed[]|null
     *
     * @throws IdentifierNotFound
     * @throws Exception\UnableToCompileNode If a referenced constant could not be located on the expected referenced class.
     */
    private function compileClassConstFetch(Node\Expr\ClassConstFetch $node, CompilerContext $context)
    {
        /** @var Node\Identifier $node->name */
        $nodeName = $node->name->name;
        /** @var Node\Name $node->class */
        $className = $node->class->toString();

        if ($nodeName === 'class') {
            return $className;
        }

        /** @var ReflectionClass|null $classInfo */
        $classInfo = null;

        if ($className === 'self' || $className === 'static') {
            $classInfo = $this->getConstantDeclaringClass($nodeName, $context->getSelf());
        }

        if ($classInfo === null) {
            /** @var ReflectionClass $classInfo */
            $classInfo = $context->getReflector()->reflect($className);
        }

        $reflectionConstant = $classInfo->getReflectionConstant($nodeName);

        if (! $reflectionConstant instanceof ReflectionClassConstant) {
            throw Exception\UnableToCompileNode::becauseOfNotFoundClassConstantReference($context, $classInfo, $node);
        }

        return $this->__invoke(
            $reflectionConstant->getAst()->consts[$reflectionConstant->getPositionInAst()]->value,
            new CompilerContext($context->getReflector(), $classInfo)
        );
    }

    /**
     * Compile a __DIR__ node
     */
    private function compileDirConstant(CompilerContext $context) : string
    {
        return FileHelper::normalizeWindowsPath(dirname(realpath($context->getFileName())));
    }

    /**
     * Compiles magic constant __CLASS__
     */
    private function compileClassConstant(CompilerContext $context) : string
    {
        return $context->hasSelf() ? $context->getSelf()->getName() : '';
    }

    private function getConstantDeclaringClass(string $constantName, ReflectionClass $class) : ?ReflectionClass
    {
        if ($class->hasConstant($constantName)) {
            return $class;
        }

        $parentClass = $class->getParentClass();

        return $parentClass ? $this->getConstantDeclaringClass($constantName, $parentClass) : null;
    }
}


<?php

declare(strict_types=1);

namespace Roave\BetterReflection\Util;

use InvalidArgumentException;
use Roave\BetterReflection\Identifier\IdentifierType;
use Roave\BetterReflection\Reflection\Reflection;
use Roave\BetterReflection\Reflection\ReflectionClass;
use Roave\BetterReflection\Reflection\ReflectionFunction;
use Roave\BetterReflection\Reflection\ReflectionMethod;
use Roave\BetterReflection\Reflector\ClassReflector;
use Roave\BetterReflection\SourceLocator\Ast\Exception\ParseToAstFailure;
use Roave\BetterReflection\SourceLocator\Ast\Locator;
use Roave\BetterReflection\SourceLocator\Exception\InvalidFileLocation;
use Roave\BetterReflection\SourceLocator\Type\AggregateSourceLocator;
use Roave\BetterReflection\SourceLocator\Type\SingleFileSourceLocator;
use Roave\BetterReflection\SourceLocator\Type\SourceLocator;
use function array_merge;
use function method_exists;

final class FindReflectionOnLine
{
    /** @var SourceLocator */
    private $sourceLocator;

    /** @var Locator */
    private $astLocator;

    public function __construct(SourceLocator $sourceLocator, Locator $astLocator)
    {
        $this->sourceLocator = $sourceLocator;
        $this->astLocator    = $astLocator;
    }

    /**
     * Find a reflection on the specified line number.
     *
     * Returns null if no reflections found on the line.
     *
     * @return ReflectionMethod|ReflectionClass|ReflectionFunction|Reflection|null
     *
     * @throws InvalidFileLocation
     * @throws ParseToAstFailure
     * @throws InvalidArgumentException
     */
    public function __invoke(string $filename, int $lineNumber)
    {
        $reflections = $this->computeReflections($filename);

        foreach ($reflections as $reflection) {
            if ($reflection instanceof ReflectionClass && $this->containsLine($reflection, $lineNumber)) {
                foreach ($reflection->getMethods() as $method) {
                    if ($this->containsLine($method, $lineNumber)) {
                        return $method;
                    }
                }

                return $reflection;
            }

            if ($reflection instanceof ReflectionFunction && $this->containsLine($reflection, $lineNumber)) {
                return $reflection;
            }
        }

        return null;
    }

    /**
     * Find all class and function reflections in the specified file
     *
     * @return Reflection[]
     *
     * @throws ParseToAstFailure
     * @throws InvalidFileLocation
     */
    private function computeReflections(string $filename) : array
    {
        $singleFileSourceLocator = new SingleFileSourceLocator($filename, $this->astLocator);
        $reflector               = new ClassReflector(new AggregateSourceLocator([$singleFileSourceLocator, $this->sourceLocator]));

        return array_merge(
            $singleFileSourceLocator->locateIdentifiersByType($reflector, new IdentifierType(IdentifierType::IDENTIFIER_CLASS)),
            $singleFileSourceLocator->locateIdentifiersByType($reflector, new IdentifierType(IdentifierType::IDENTIFIER_FUNCTION))
        );
    }

    /**
     * Check to see if the line is within the boundaries of the reflection specified.
     *
     * @param ReflectionMethod|ReflectionClass|ReflectionFunction|Reflection $reflection
     *
     * @throws InvalidArgumentException
     */
    private function containsLine($reflection, int $lineNumber) : bool
    {
        if (! method_exists($reflection, 'getStartLine')) {
            throw new InvalidArgumentException('Reflection does not have getStartLine method');
        }

        if (! method_exists($reflection, 'getEndLine')) {
            throw new InvalidArgumentException('Reflection does not have getEndLine method');
        }

        return $lineNumber >= $reflection->getStartLine() && $lineNumber <= $reflection->getEndLine();
    }
}


<?php

declare(strict_types=1);

namespace Roave\BetterReflection\Reflector;

use Roave\BetterReflection\Identifier\Identifier;
use Roave\BetterReflection\Identifier\IdentifierType;
use Roave\BetterReflection\Reflection\Reflection;
use Roave\BetterReflection\Reflection\ReflectionClass;
use Roave\BetterReflection\Reflector\Exception\IdentifierNotFound;
use Roave\BetterReflection\SourceLocator\Type\SourceLocator;

class ClassReflector implements Reflector
{
    /** @var SourceLocator */
    private $sourceLocator;

    public function __construct(SourceLocator $sourceLocator)
    {
        $this->sourceLocator = $sourceLocator;
    }

    /**
     * Create a ReflectionClass for the specified $className.
     *
     * @return ReflectionClass
     *
     * @throws IdentifierNotFound
     */
    public function reflect(string $className) : Reflection
    {
        $identifier = new Identifier($className, new IdentifierType(IdentifierType::IDENTIFIER_CLASS));

        /** @var ReflectionClass|null $classInfo */
        $classInfo = $this->sourceLocator->locateIdentifier($this, $identifier);

        if ($classInfo === null) {
            throw Exception\IdentifierNotFound::fromIdentifier($identifier);
        }

        return $classInfo;
    }

    /**
     * Get all the classes available in the scope specified by the SourceLocator.
     *
     * @return ReflectionClass[]
     */
    public function getAllClasses() : array
    {
        /** @var ReflectionClass[] $allClasses */
        $allClasses = $this->sourceLocator->locateIdentifiersByType(
            $this,
            new IdentifierType(IdentifierType::IDENTIFIER_CLASS)
        );

        return $allClasses;
    }
}


<?php

declare(strict_types=1);

namespace Roave\BetterReflection\Reflection;

use Closure;
use Exception;
use InvalidArgumentException;
use phpDocumentor\Reflection\Type;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\Namespace_;
use PhpParser\Node\Stmt\Property as PropertyNode;
use ReflectionException;
use ReflectionProperty as CoreReflectionProperty;
use Reflector as CoreReflector;
use Roave\BetterReflection\NodeCompiler\CompileNodeToValue;
use Roave\BetterReflection\NodeCompiler\CompilerContext;
use Roave\BetterReflection\Reflection\Exception\ClassDoesNotExist;
use Roave\BetterReflection\Reflection\Exception\NoObjectProvided;
use Roave\BetterReflection\Reflection\Exception\NotAnObject;
use Roave\BetterReflection\Reflection\Exception\ObjectNotInstanceOfClass;
use Roave\BetterReflection\Reflection\Exception\Uncloneable;
use Roave\BetterReflection\Reflection\StringCast\ReflectionPropertyStringCast;
use Roave\BetterReflection\Reflector\Exception\IdentifierNotFound;
use Roave\BetterReflection\Reflector\Reflector;
use Roave\BetterReflection\TypesFinder\FindPropertyType;
use Roave\BetterReflection\Util\CalculateReflectionColum;
use Roave\BetterReflection\Util\GetFirstDocComment;
use function class_exists;
use function func_num_args;
use function get_class;
use function is_object;

class ReflectionProperty implements CoreReflector
{
    /** @var ReflectionClass */
    private $declaringClass;

    /** @var ReflectionClass */
    private $implementingClass;

    /** @var PropertyNode */
    private $node;

    /** @var int */
    private $positionInNode;

    /** @var Namespace_|null */
    private $declaringNamespace;

    /** @var bool */
    private $declaredAtCompileTime = true;

    /** @var Reflector */
    private $reflector;

    private function __construct()
    {
    }

    public static function export() : void
    {
        throw new Exception('Unable to export statically');
    }

    /**
     * Create a reflection of a class's property by its name
     */
    public static function createFromName(string $className, string $propertyName) : self
    {
        return ReflectionClass::createFromName($className)->getProperty($propertyName);
    }

    /**
     * Create a reflection of an instance's property by its name
     *
     * @param object $instance
     *
     * @throws InvalidArgumentException
     * @throws ReflectionException
     * @throws IdentifierNotFound
     */
    public static function createFromInstance($instance, string $propertyName) : self
    {
        return ReflectionClass::createFromInstance($instance)->getProperty($propertyName);
    }

    public function __toString() : string
    {
        return ReflectionPropertyStringCast::toString($this);
    }

    /**
     * @internal
     *
     * @param PropertyNode $node Node has to be processed by the PhpParser\NodeVisitor\NameResolver
     */
    public static function createFromNode(
        Reflector $reflector,
        PropertyNode $node,
        int $positionInNode,
        ?Namespace_ $declaringNamespace,
        ReflectionClass $declaringClass,
        ReflectionClass $implementingClass,
        bool $declaredAtCompileTime = true
    ) : self {
        $prop                        = new self();
        $prop->reflector             = $reflector;
        $prop->node                  = $node;
        $prop->positionInNode        = $positionInNode;
        $prop->declaringNamespace    = $declaringNamespace;
        $prop->declaringClass        = $declaringClass;
        $prop->implementingClass     = $implementingClass;
        $prop->declaredAtCompileTime = $declaredAtCompileTime;

        return $prop;
    }

    /**
     * Set the default visibility of this property. Use the core \ReflectionProperty::IS_* values as parameters, e.g.:
     *
     * @throws InvalidArgumentException
     */
    public function setVisibility(int $newVisibility) : void
    {
        $this->node->flags &= ~Class_::MODIFIER_PRIVATE & ~Class_::MODIFIER_PROTECTED & ~Class_::MODIFIER_PUBLIC;

        switch ($newVisibility) {
            case CoreReflectionProperty::IS_PRIVATE:
                $this->node->flags |= Class_::MODIFIER_PRIVATE;
                break;
            case CoreReflectionProperty::IS_PROTECTED:
                $this->node->flags |= Class_::MODIFIER_PROTECTED;
                break;
            case CoreReflectionProperty::IS_PUBLIC:
                $this->node->flags |= Class_::MODIFIER_PUBLIC;
                break;
            default:
                throw new InvalidArgumentException('Visibility should be \ReflectionProperty::IS_PRIVATE, ::IS_PROTECTED or ::IS_PUBLIC constants');
        }
    }

    /**
     * Has the property been declared at compile-time?
     *
     * Note that unless the property is static, this is hard coded to return
     * true, because we are unable to reflect instances of classes, therefore
     * we can be sure that all properties are always declared at compile-time.
     */
    public function isDefault() : bool
    {
        return $this->declaredAtCompileTime;
    }

    /**
     * Get the core-reflection-compatible modifier values.
     */
    public function getModifiers() : int
    {
        $val  = 0;
        $val += $this->isStatic() ? CoreReflectionProperty::IS_STATIC : 0;
        $val += $this->isPublic() ? CoreReflectionProperty::IS_PUBLIC : 0;
        $val += $this->isProtected() ? CoreReflectionProperty::IS_PROTECTED : 0;
        $val += $this->isPrivate() ? CoreReflectionProperty::IS_PRIVATE : 0;

        return $val;
    }

    /**
     * Get the name of the property.
     */
    public function getName() : string
    {
        return $this->node->props[$this->positionInNode]->name->name;
    }

    /**
     * Is the property private?
     */
    public function isPrivate() : bool
    {
        return $this->node->isPrivate();
    }

    /**
     * Is the property protected?
     */
    public function isProtected() : bool
    {
        return $this->node->isProtected();
    }

    /**
     * Is the property public?
     */
    public function isPublic() : bool
    {
        return $this->node->isPublic();
    }

    /**
     * Is the property static?
     */
    public function isStatic() : bool
    {
        return $this->node->isStatic();
    }

    /**
     * Get the DocBlock type hints as an array of strings.
     *
     * @return string[]
     */
    public function getDocBlockTypeStrings() : array
    {
        $stringTypes = [];

        foreach ($this->getDocBlockTypes() as $type) {
            $stringTypes[] = (string) $type;
        }

        return $stringTypes;
    }

    /**
     * Get the types defined in the DocBlocks. This returns an array because
     * the parameter may have multiple (compound) types specified (for example
     * when you type hint pipe-separated "string|null", in which case this
     * would return an array of Type objects, one for string, one for null.
     *
     * @return Type[]
     */
    public function getDocBlockTypes() : array
    {
        return (new FindPropertyType())->__invoke($this, $this->declaringNamespace);
    }

    public function getDeclaringClass() : ReflectionClass
    {
        return $this->declaringClass;
    }

    public function getImplementingClass() : ReflectionClass
    {
        return $this->implementingClass;
    }

    public function getDocComment() : string
    {
        return GetFirstDocComment::forNode($this->node);
    }

    /**
     * Get the default value of the property (as defined before constructor is
     * called, when the property is defined)
     *
     * @return mixed
     */
    public function getDefaultValue()
    {
        $defaultValueNode = $this->node->props[$this->positionInNode]->default;

        if ($defaultValueNode === null) {
            return null;
        }

        return (new CompileNodeToValue())->__invoke(
            $defaultValueNode,
            new CompilerContext($this->reflector, $this->getDeclaringClass())
        );
    }

    /**
     * Get the line number that this property starts on.
     */
    public function getStartLine() : int
    {
        return $this->node->getStartLine();
    }

    /**
     * Get the line number that this property ends on.
     */
    public function getEndLine() : int
    {
        return $this->node->getEndLine();
    }

    public function getStartColumn() : int
    {
        return CalculateReflectionColum::getStartColumn($this->declaringClass->getLocatedSource()->getSource(), $this->node);
    }

    public function getEndColumn() : int
    {
        return CalculateReflectionColum::getEndColumn($this->declaringClass->getLocatedSource()->getSource(), $this->node);
    }

    public function getAst() : PropertyNode
    {
        return $this->node;
    }

    public function getPositionInAst() : int
    {
        return $this->positionInNode;
    }

    /**
     * {@inheritdoc}
     *
     * @throws Uncloneable
     */
    public function __clone()
    {
        throw Uncloneable::fromClass(self::class);
    }

    /**
     * @param object|null $object
     *
     * @return mixed
     *
     * @throws ClassDoesNotExist
     * @throws NoObjectProvided
     * @throws NotAnObject
     * @throws ObjectNotInstanceOfClass
     */
    public function getValue($object = null)
    {
        $declaringClassName = $this->getDeclaringClass()->getName();

        if ($this->isStatic()) {
            $this->assertClassExist($declaringClassName);

            return Closure::bind(function (string $declaringClassName, string $propertyName) {
                return $declaringClassName::${$propertyName};
            }, null, $declaringClassName)->__invoke($declaringClassName, $this->getName());
        }

        $instance = $this->assertObject($object);

        return Closure::bind(function ($instance, string $propertyName) {
            return $instance->{$propertyName};
        }, $instance, $declaringClassName)->__invoke($instance, $this->getName());
    }

    /**
     * @param object     $object
     * @param mixed|null $value
     *
     * @throws ClassDoesNotExist
     * @throws NoObjectProvided
     * @throws NotAnObject
     * @throws ObjectNotInstanceOfClass
     */
    public function setValue($object, $value = null) : void
    {
        $declaringClassName = $this->getDeclaringClass()->getName();

        if ($this->isStatic()) {
            $this->assertClassExist($declaringClassName);

            Closure::bind(function (string $declaringClassName, string $propertyName, $value) : void {
                $declaringClassName::${$propertyName} = $value;
            }, null, $declaringClassName)->__invoke($declaringClassName, $this->getName(), func_num_args() === 2 ? $value : $object);

            return;
        }

        $instance = $this->assertObject($object);

        Closure::bind(function ($instance, string $propertyName, $value) : void {
            $instance->{$propertyName} = $value;
        }, $instance, $declaringClassName)->__invoke($instance, $this->getName(), $value);
    }

    /**
     * @throws ClassDoesNotExist
     */
    private function assertClassExist(string $className) : void
    {
        if (! class_exists($className, false)) {
            throw new ClassDoesNotExist('Property cannot be retrieved as the class is not loaded');
        }
    }

    /**
     * @param mixed $object
     *
     * @return object
     *
     * @throws NoObjectProvided
     * @throws NotAnObject
     * @throws ObjectNotInstanceOfClass
     */
    private function assertObject($object)
    {
        if ($object === null) {
            throw NoObjectProvided::create();
        }

        if (! is_object($object)) {
            throw NotAnObject::fromNonObject($object);
        }

        $declaringClassName = $this->getDeclaringClass()->getName();

        if (get_class($object) !== $declaringClassName) {
            throw ObjectNotInstanceOfClass::fromClassName($declaringClassName);
        }

        return $object;
    }
}


<?php

declare(strict_types=1);

namespace Roave\BetterReflection\SourceLocator\Type;

use InvalidArgumentException;
use PhpParser\Node;
use PhpParser\Node\Stmt\Class_;
use PhpParser\NodeTraverser;
use PhpParser\NodeVisitorAbstract;
use PhpParser\Parser;
use ReflectionClass as CoreReflectionClass;
use ReflectionException;
use Roave\BetterReflection\Identifier\Identifier;
use Roave\BetterReflection\Identifier\IdentifierType;
use Roave\BetterReflection\Reflection\Reflection;
use Roave\BetterReflection\Reflection\ReflectionClass;
use Roave\BetterReflection\Reflector\Reflector;
use Roave\BetterReflection\SourceLocator\Ast\Exception\ParseToAstFailure;
use Roave\BetterReflection\SourceLocator\Ast\Strategy\NodeToReflection;
use Roave\BetterReflection\SourceLocator\Exception\EvaledAnonymousClassCannotBeLocated;
use Roave\BetterReflection\SourceLocator\Exception\TwoAnonymousClassesOnSameLine;
use Roave\BetterReflection\SourceLocator\FileChecker;
use Roave\BetterReflection\SourceLocator\Located\LocatedSource;
use Roave\BetterReflection\Util\FileHelper;
use function array_filter;
use function array_values;
use function file_get_contents;
use function gettype;
use function is_object;
use function sprintf;
use function strpos;

/**
 * @internal
 */
final class AnonymousClassObjectSourceLocator implements SourceLocator
{
    /** @var CoreReflectionClass */
    private $coreClassReflection;

    /** @var Parser */
    private $parser;

    /**
     * @param object $anonymousClassObject
     *
     * @throws InvalidArgumentException
     * @throws ReflectionException
     *
     * @psalm-suppress DocblockTypeContradiction
     */
    public function __construct($anonymousClassObject, Parser $parser)
    {
        if (! is_object($anonymousClassObject)) {
            throw new InvalidArgumentException(sprintf(
                'Can only create from an instance of an object, "%s" given',
                gettype($anonymousClassObject)
            ));
        }

        $this->coreClassReflection = new CoreReflectionClass($anonymousClassObject);
        $this->parser              = $parser;
    }

    /**
     * {@inheritDoc}
     *
     * @throws ParseToAstFailure
     */
    public function locateIdentifier(Reflector $reflector, Identifier $identifier) : ?Reflection
    {
        return $this->getReflectionClass($reflector, $identifier->getType());
    }

    /**
     * {@inheritDoc}
     *
     * @throws ParseToAstFailure
     */
    public function locateIdentifiersByType(Reflector $reflector, IdentifierType $identifierType) : array
    {
        return array_filter([$this->getReflectionClass($reflector, $identifierType)]);
    }

    private function getReflectionClass(Reflector $reflector, IdentifierType $identifierType) : ?ReflectionClass
    {
        if (! $identifierType->isClass()) {
            return null;
        }

        $fileName = $this->coreClassReflection->getFileName();

        if (strpos($fileName, 'eval()\'d code') !== false) {
            throw EvaledAnonymousClassCannotBeLocated::create();
        }

        FileChecker::assertReadableFile($fileName);

        $fileName = FileHelper::normalizeWindowsPath($fileName);

        $nodeVisitor = new class($fileName, $this->coreClassReflection->getStartLine()) extends NodeVisitorAbstract
        {
            /** @var string */
            private $fileName;

            /** @var int */
            private $startLine;

            /** @var Class_[] */
            private $anonymousClassNodes = [];

            public function __construct(string $fileName, int $startLine)
            {
                $this->fileName  = $fileName;
                $this->startLine = $startLine;
            }

            /**
             * {@inheritDoc}
             */
            public function enterNode(Node $node)
            {
                if (! ($node instanceof Node\Stmt\Class_) || $node->name !== null) {
                    return null;
                }

                $this->anonymousClassNodes[] = $node;
            }

            public function getAnonymousClassNode() : ?Class_
            {
                /** @var Class_[] $anonymousClassNodesOnSameLine */
                $anonymousClassNodesOnSameLine = array_values(array_filter($this->anonymousClassNodes, function (Class_ $node) : bool {
                    return $node->getLine() === $this->startLine;
                }));

                if (! $anonymousClassNodesOnSameLine) {
                    return null;
                }

                if (isset($anonymousClassNodesOnSameLine[1])) {
                    throw TwoAnonymousClassesOnSameLine::create($this->fileName, $this->startLine);
                }

                return $anonymousClassNodesOnSameLine[0];
            }
        };

        $fileContents = file_get_contents($fileName);
        $ast          = $this->parser->parse($fileContents);

        $nodeTraverser = new NodeTraverser();
        $nodeTraverser->addVisitor($nodeVisitor);
        $nodeTraverser->traverse($ast);

        /** @var ReflectionClass|null $reflectionClass */
        $reflectionClass = (new NodeToReflection())->__invoke(
            $reflector,
            $nodeVisitor->getAnonymousClassNode(),
            new LocatedSource($fileContents, $fileName),
            null
        );

        return $reflectionClass;
    }
}


<?php

declare(strict_types=1);

namespace Roave\BetterReflection\SourceLocator\Type\Composer\Psr;

use Roave\BetterReflection\Identifier\Identifier;
use Roave\BetterReflection\SourceLocator\Type\Composer\Psr\Exception\InvalidPrefixMapping;
use function array_map;
use function array_merge;
use function array_unique;
use function array_values;
use function is_dir;
use function rtrim;
use function str_replace;
use function strpos;

final class Psr0Mapping implements PsrAutoloaderMapping
{
    /** @var array<string, array<int, string>> */
    private $mappings = [];

    private function __construct()
    {
    }

    /** @param array<string, array<int, string>> $mappings */
    public static function fromArrayMappings(array $mappings) : self
    {
        self::assertValidMapping($mappings);

        $instance = new self();

        $instance->mappings = array_map(
            static function (array $directories) : array {
                return array_map(static function (string $directory) : string {
                    return rtrim($directory, '/');
                }, $directories);
            },
            $mappings
        );

        return $instance;
    }

    /** {@inheritDoc} */
    public function resolvePossibleFilePaths(Identifier $identifier) : array
    {
        if (! $identifier->isClass()) {
            return [];
        }

        $className = $identifier->getName();

        foreach ($this->mappings as $prefix => $paths) {
            if (strpos($className, $prefix) === 0) {
                return array_map(
                    static function (string $path) use ($className) : string {
                        return rtrim($path, '/') . '/' . str_replace(['\\', '_'], '/', $className) . '.php';
                    },
                    $paths
                );
            }
        }

        return [];
    }

    /** {@inheritDoc} */
    public function directories() : array
    {
        return array_values(array_unique(array_merge([], ...array_values($this->mappings))));
    }

    /**
     * @param array<string, array<int, string>> $mappings
     *
     * @throws InvalidPrefixMapping
     */
    private static function assertValidMapping(array $mappings) : void
    {
        foreach ($mappings as $prefix => $paths) {
            if ($prefix === '') {
                throw InvalidPrefixMapping::emptyPrefixGiven();
            }

            if ($paths === []) {
                throw InvalidPrefixMapping::emptyPrefixMappingGiven($prefix);
            }

            foreach ($paths as $path) {
                if (! is_dir($path)) {
                    throw InvalidPrefixMapping::prefixMappingIsNotADirectory($prefix, $path);
                }
            }
        }
    }
}


<?php

declare(strict_types=1);

namespace Roave\BetterReflection\Reflection;

use Closure;
use PhpParser\Node;
use PhpParser\Node\FunctionLike as FunctionNode;
use PhpParser\Node\Stmt\Namespace_ as NamespaceNode;
use Roave\BetterReflection\BetterReflection;
use Roave\BetterReflection\Reflection\Adapter\Exception\NotImplemented;
use Roave\BetterReflection\Reflection\Exception\FunctionDoesNotExist;
use Roave\BetterReflection\Reflection\StringCast\ReflectionFunctionStringCast;
use Roave\BetterReflection\Reflector\Exception\IdentifierNotFound;
use Roave\BetterReflection\Reflector\FunctionReflector;
use Roave\BetterReflection\Reflector\Reflector;
use Roave\BetterReflection\SourceLocator\Located\LocatedSource;
use Roave\BetterReflection\SourceLocator\Type\ClosureSourceLocator;
use function function_exists;

class ReflectionFunction extends ReflectionFunctionAbstract implements Reflection
{
    /**
     * @throws IdentifierNotFound
     */
    public static function createFromName(string $functionName) : self
    {
        return (new BetterReflection())->functionReflector()->reflect($functionName);
    }

    /**
     * @throws IdentifierNotFound
     */
    public static function createFromClosure(Closure $closure) : self
    {
        $configuration = new BetterReflection();

        return (new FunctionReflector(
            new ClosureSourceLocator($closure, $configuration->phpParser()),
            $configuration->classReflector()
        ))->reflect(self::CLOSURE_NAME);
    }

    public function __toString() : string
    {
        return ReflectionFunctionStringCast::toString($this);
    }

    /**
     * @internal
     *
     * @param Node\Stmt\ClassMethod|Node\Stmt\Function_|Node\Expr\Closure $node Node has to be processed by the PhpParser\NodeVisitor\NameResolver
     */
    public static function createFromNode(
        Reflector $reflector,
        FunctionNode $node,
        LocatedSource $locatedSource,
        ?NamespaceNode $namespaceNode = null
    ) : self {
        $function = new self();

        $function->populateFunctionAbstract($reflector, $node, $locatedSource, $namespaceNode);

        return $function;
    }

    /**
     * Check to see if this function has been disabled (by the PHP INI file
     * directive `disable_functions`).
     *
     * Note - we cannot reflect on internal functions (as there is no PHP source
     * code we can access. This means, at present, we can only EVER return false
     * from this function, because you cannot disable user-defined functions.
     *
     * @see https://php.net/manual/en/ini.core.php#ini.disable-functions
     *
     * @todo https://github.com/Roave/BetterReflection/issues/14
     */
    public function isDisabled() : bool
    {
        return false;
    }

    /**
     * @throws NotImplemented
     * @throws FunctionDoesNotExist
     */
    public function getClosure() : Closure
    {
        $this->assertIsNoClosure();

        $functionName = $this->getName();

        $this->assertFunctionExist($functionName);

        return static function (...$args) use ($functionName) {
            /** @var callable $functionName */
            return $functionName(...$args);
        };
    }

    /**
     * @param mixed ...$args
     *
     * @return mixed
     *
     * @throws NotImplemented
     * @throws FunctionDoesNotExist
     */
    public function invoke(...$args)
    {
        return $this->invokeArgs($args);
    }

    /**
     * @param mixed[] $args
     *
     * @return mixed
     *
     * @throws NotImplemented
     * @throws FunctionDoesNotExist
     */
    public function invokeArgs(array $args = [])
    {
        $this->assertIsNoClosure();

        $functionName = $this->getName();

        $this->assertFunctionExist($functionName);

        /** @var callable $functionName */
        return $functionName(...$args);
    }

    /**
     * @throws NotImplemented
     */
    private function assertIsNoClosure() : void
    {
        if ($this->isClosure()) {
            throw new NotImplemented('Not implemented for closures');
        }
    }

    /**
     * @throws FunctionDoesNotExist
     */
    private function assertFunctionExist(string $functionName) : void
    {
        if (! function_exists($functionName)) {
            throw FunctionDoesNotExist::fromName($functionName);
        }
    }
}


<?php

declare(strict_types=1);

namespace Roave\BetterReflection\SourceLocator\Type;

use Composer\Autoload\ClassLoader;
use InvalidArgumentException;
use Roave\BetterReflection\Identifier\Identifier;
use Roave\BetterReflection\Identifier\IdentifierType;
use Roave\BetterReflection\SourceLocator\Ast\Locator;
use Roave\BetterReflection\SourceLocator\Exception\InvalidFileLocation;
use Roave\BetterReflection\SourceLocator\Located\LocatedSource;
use function file_get_contents;

/**
 * This source locator uses Composer's built-in ClassLoader to locate files.
 *
 * Note that we use ClassLoader->findFile directory, rather than
 * ClassLoader->loadClass because this library has a strict requirement that we
 * do NOT actually load the classes
 */
class ComposerSourceLocator extends AbstractSourceLocator
{
    /** @var ClassLoader */
    private $classLoader;

    public function __construct(ClassLoader $classLoader, Locator $astLocator)
    {
        parent::__construct($astLocator);
        $this->classLoader = $classLoader;
    }

    /**
     * {@inheritDoc}
     *
     * @throws InvalidArgumentException
     * @throws InvalidFileLocation
     */
    protected function createLocatedSource(Identifier $identifier) : ?LocatedSource
    {
        if ($identifier->getType()->getName() !== IdentifierType::IDENTIFIER_CLASS) {
            return null;
        }

        $filename = $this->classLoader->findFile($identifier->getName());

        if (! $filename) {
            return null;
        }

        return new LocatedSource(
            file_get_contents($filename),
            $filename
        );
    }
}