<commit_before><?php declare(strict_types=1);

namespace Rector\ContributorTools\Configuration;

use Nette\Loaders\RobotLoader;
use Nette\Utils\Strings;
use Rector\ContributorTools\Exception\ConfigurationException;
use Rector\Exception\FileSystem\FileNotFoundException;
use Symfony\Component\Yaml\Yaml;
use function Safe\getcwd;
use function Safe\sprintf;

final class ConfigurationFactory
{
    public function createFromConfigFile(string $configFile): Configuration
    {
        $this->ensureConfigFileIsFound($configFile);

        $config = (array) Yaml::parseFile($configFile);
        $this->ensureConfigIsValid($config, $configFile);

        return new Configuration(
            $config['package'],
            $config['name'],
            $config['node_types'][0],
            $this->resolveFullyQualifiedNodeTypes($config['node_types']),
            $config['description'],
            $config['code_before'],
            $config['code_after']
        );
    }

    private function ensureConfigFileIsFound(string $configFile): void
    {
        if (file_exists($configFile)) {
            return;
        }

        throw new FileNotFoundException(sprintf('First, create config file "%s"', $configFile));
    }

    /**
     * @param mixed[] $config
     */
    private function ensureConfigIsValid(array $config, string $configFile): void
    {
        $requiredKeys = ['package', 'name', 'node_types', 'code_before', 'code_after', 'description'];
        if (count(array_intersect(array_keys($config), $requiredKeys)) === count($requiredKeys)) {
            if (count($config['node_types']) < 1) {
                throw new ConfigurationException(sprintf(
                    '"%s" option requires at least one node, e.g. "FuncCall"',
                    'node_types'
                ));
            }

            return;
        }

        throw new ConfigurationException(sprintf(
            'Make sure "%s" keys are present in the "%s" config',
            implode('", "', $requiredKeys),
            $configFile
        ));
    }

    /**
     * @param string[] $nodeTypes
     * @return string[]
     */
    private function resolveFullyQualifiedNodeTypes(array $nodeTypes): array
    {
        $robotLoader = new RobotLoader();
        $robotLoader->addDirectory(__DIR__ . '/../../../../vendor/nikic/php-parser/lib/PhpParser/Node');
        $robotLoader->setTempDirectory(getcwd() . '_robotloader_nodes');
        $robotLoader->rebuild();

        $nodeClasses = array_keys($robotLoader->getIndexedClasses());

        $fqnNodeTypes = [];
        foreach ($nodeTypes as $nodeType) {
            foreach ($nodeClasses as $nodeClass) {
                if (Strings::endsWith($nodeClass, $nodeType)) {
                    $fqnNodeTypes[] = $nodeClass;
                }
            }
        }

        return array_unique($fqnNodeTypes);<commit_msg>move RobotLoader cache to tmp instead of root<commit_after><?php declare(strict_types=1);

namespace Rector\ContributorTools\Configuration;

use Nette\Loaders\RobotLoader;
use Nette\Utils\Strings;
use Rector\ContributorTools\Exception\ConfigurationException;
use Rector\Exception\FileSystem\FileNotFoundException;
use Symfony\Component\Yaml\Yaml;
use function Safe\getcwd;
use function Safe\sprintf;

final class ConfigurationFactory
{
    public function createFromConfigFile(string $configFile): Configuration
    {
        $this->ensureConfigFileIsFound($configFile);

        $config = (array) Yaml::parseFile($configFile);
        $this->ensureConfigIsValid($config, $configFile);

        return new Configuration(
            $config['package'],
            $config['name'],
            $config['node_types'][0],
            $this->resolveFullyQualifiedNodeTypes($config['node_types']),
            $config['description'],
            $config['code_before'],
            $config['code_after']
        );
    }

    private function ensureConfigFileIsFound(string $configFile): void
    {
        if (file_exists($configFile)) {
            return;
        }

        throw new FileNotFoundException(sprintf('First, create config file "%s"', $configFile));
    }

    /**
     * @param mixed[] $config
     */
    private function ensureConfigIsValid(array $config, string $configFile): void
    {
        $requiredKeys = ['package', 'name', 'node_types', 'code_before', 'code_after', 'description'];
        if (count(array_intersect(array_keys($config), $requiredKeys)) === count($requiredKeys)) {
            if (count($config['node_types']) < 1) {
                throw new ConfigurationException(sprintf(
                    '"%s" option requires at least one node, e.g. "FuncCall"',
                    'node_types'
                ));
            }

            return;
        }

        throw new ConfigurationException(sprintf(
            'Make sure "%s" keys are present in the "%s" config',
            implode('", "', $requiredKeys),
            $configFile
        ));
    }

    /**
     * @param string[] $nodeTypes
     * @return string[]
     */
    private function resolveFullyQualifiedNodeTypes(array $nodeTypes): array
    {
        $robotLoader = new RobotLoader();
        $robotLoader->addDirectory(__DIR__ . '/../../../../vendor/nikic/php-parser/lib/PhpParser/Node');
        $robotLoader->setTempDirectory(sys_get_temp_dir() . '/_robotloader_nodes');
        $robotLoader->rebuild();

        $nodeClasses = array_keys($robotLoader->getIndexedClasses());

        $fqnNodeTypes = [];
        foreach ($nodeTypes as $nodeType) {
            foreach ($nodeClasses as $nodeClass) {
                if (Strings::endsWith($nodeClass, $nodeType)) {
                    $fqnNodeTypes[] = $nodeClass;
                }
            }
        }

        return array_unique($fqnNodeTypes);
{'fraction_non_alphanumeric': 0.1305550752204738, 'fraction_numerical': 0.0010375237765865469, 'mean_word_length': 2.4864376130198913, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 17, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '829813', 'n_tokens_mistral': 1589, 'n_tokens_neox': 1554, 'n_words': 306}