<?php

/**
 * Test the Ruleset::registerSniffs() method.
 *
 * @author    Juliette Reinders Folmer <phpcs_nospam@adviesenzo.nl>
 * @copyright 2024 PHPCSStandards and contributors
 * @license   https://github.com/PHPCSStandards/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
 */
namespace PHP_CodeSniffer\Tests\Core\Ruleset;

use PHP_CodeSniffer\Ruleset;
use PHP_CodeSniffer\Tests\ConfigDouble;
use ECSPrefix202510\PHPUnit\Framework\TestCase;
/**
 * Test the Ruleset::registerSniffs() method.
 *
 * @covers \PHP_CodeSniffer\Ruleset::registerSniffs
 */
final class RegisterSniffsTest extends TestCase
{
    /**
     * The Ruleset object.
     *
     * @var \PHP_CodeSniffer\Ruleset
     */
    private static $ruleset;
    /**
     * Original value of the $sniffs property on the Ruleset.
     *
     * @var array<string, \PHP_CodeSniffer\Sniffs\Sniff>
     */
    private static $originalSniffs = [];
    /**
     * List of Standards dir relative sniff files loaded for the PSR1 standard.
     *
     * @var array<string>
     */
    private static $psr1SniffFiles = ['Generic/Sniffs/Files/ByteOrderMarkSniff.php', 'Generic/Sniffs/NamingConventions/UpperCaseConstantNameSniff.php', 'Generic/Sniffs/PHP/DisallowAlternativePHPTagsSniff.php', 'Generic/Sniffs/PHP/DisallowShortOpenTagSniff.php', 'PSR1/Sniffs/Classes/ClassDeclarationSniff.php', 'PSR1/Sniffs/Files/SideEffectsSniff.php', 'PSR1/Sniffs/Methods/CamelCapsMethodNameSniff.php', 'Squiz/Sniffs/Classes/ValidClassNameSniff.php'];
    /**
     * Absolute paths to the sniff files loaded for the PSR1 standard.
     *
     * @var array<string>
     */
    private static $psr1SniffAbsolutePaths = [];
    /**
     * Initialize the config and ruleset objects which will be used for some of these tests.
     *
     * @beforeClass
     *
     * @return void
     */
    public static function initializeConfigAndRuleset()
    {
        // Set up the ruleset.
        $config = new ConfigDouble(['--standard=PSR1']);
        self::$ruleset = new Ruleset($config);
        // Remember the original value of the Ruleset::$sniff property as the tests adjust it.
        self::$originalSniffs = self::$ruleset->sniffs;
        // Sort the value to make the tests stable as different OSes will read directories
        // in a different order and the order is not relevant for these tests. Just the values.
        \ksort(self::$originalSniffs);
        // Update the sniff file list.
        $standardsDir = \dirname(\dirname(\dirname(__DIR__))) . \DIRECTORY_SEPARATOR;
        $standardsDir .= 'src' . \DIRECTORY_SEPARATOR . 'Standards' . \DIRECTORY_SEPARATOR;
        self::$psr1SniffAbsolutePaths = self::relativeToAbsoluteSniffFiles($standardsDir, self::$psr1SniffFiles);
    }
    //end initializeConfigAndRuleset()
    /**
     * Convert relative paths to absolute paths and ensure the paths use the correct OS-specific directory separator.
     *
     * @param string        $baseDir       Directory to which these paths are relative to. Including trailing slash.
     * @param array<string> $relativePaths Relative paths.
     *
     * @return array<string>
     */
    public static function relativeToAbsoluteSniffFiles($baseDir, $relativePaths)
    {
        $fileList = [];
        foreach ($relativePaths as $sniffName) {
            $sniffFile = \str_replace('/', \DIRECTORY_SEPARATOR, $sniffName);
            $sniffFile = $baseDir . $sniffFile;
            $fileList[] = $sniffFile;
        }
        return $fileList;
    }
    //end relativeToAbsoluteSniffFiles()
    /**
     * Clear out the Ruleset::$sniffs property.
     *
     * @before
     *
     * @return void
     */
    protected function clearOutSniffs()
    {
        // Clear out the Ruleset::$sniffs property.
        self::$ruleset->sniffs = [];
    }
    //end clearOutSniffs()
    /**
     * Test that registering sniffs works as expected (simple base test case).
     *
     * @return void
     */
    public function testRegisteredSniffsShouldBeTheSame()
    {
        self::$ruleset->registerSniffs(self::$psr1SniffAbsolutePaths, [], []);
        // Make sure the same sniff list was recreated (but without the objects having been created yet).
        $this->assertSame(\array_keys(self::$originalSniffs), \array_keys(self::$ruleset->sniffs));
        $this->assertSame(\array_keys(self::$originalSniffs), \array_values(self::$ruleset->sniffs));
    }
    //end testRegisteredSniffsShouldBeTheSame()
    /**
     * Test that if only specific sniffs are requested, only those are registered.
     *
     * {@internal Can't test this via the CLI arguments due to some code in the Ruleset class
     * related to sniff tests.}
     *
     * @return void
     */
    public function testRegisteredSniffsWithRestrictions()
    {
        $restrictions = ['ECSPrefix202510\\psr1\\sniffs\\classes\\classdeclarationsniff' => \true, 'ECSPrefix202510\\psr1\\sniffs\\files\\sideeffectssniff' => \true, 'ECSPrefix202510\\psr1\\sniffs\\methods\\camelcapsmethodnamesniff' => \true];
        $expected = ['PHP_CodeSniffer\\Standards\\PSR1\\Sniffs\\Classes\\ClassDeclarationSniff', 'PHP_CodeSniffer\\Standards\\PSR1\\Sniffs\\Files\\SideEffectsSniff', 'PHP_CodeSniffer\\Standards\\PSR1\\Sniffs\\Methods\\CamelCapsMethodNameSniff'];
        self::$ruleset->registerSniffs(self::$psr1SniffAbsolutePaths, $restrictions, []);
        $this->assertSame($expected, \array_keys(self::$ruleset->sniffs));
    }
    //end testRegisteredSniffsWithRestrictions()
    /**
     * Test that sniffs excluded via the CLI are not registered.
     *
     * @return void
     */
    public function testRegisteredSniffsWithExclusions()
    {
        // Set up the ruleset.
        $args = ['--standard=PSR1', '--exclude=PSR1.Classes.ClassDeclaration,PSR1.Files.SideEffects,PSR1.Methods.CamelCapsMethodName'];
        $config = new ConfigDouble($args);
        $ruleset = new Ruleset($config);
        $expected = ['PHP_CodeSniffer\\Standards\\Generic\\Sniffs\\Files\\ByteOrderMarkSniff', 'PHP_CodeSniffer\\Standards\\Generic\\Sniffs\\NamingConventions\\UpperCaseConstantNameSniff', 'PHP_CodeSniffer\\Standards\\Generic\\Sniffs\\PHP\\DisallowAlternativePHPTagsSniff', 'PHP_CodeSniffer\\Standards\\Generic\\Sniffs\\PHP\\DisallowShortOpenTagSniff', 'PHP_CodeSniffer\\Standards\\Squiz\\Sniffs\\Classes\\ValidClassNameSniff'];
        $actual = \array_keys($ruleset->sniffs);
        \sort($actual);
        $this->assertSame($expected, $actual);
    }
    //end testRegisteredSniffsWithExclusions()
    /**
     * Test combining requesting specific sniffs and excluding a subset of those.
     *
     * @return void
     */
    public function testRegisteredSniffsBothRestrictionsAndExclusions()
    {
        $restrictions = ['ECSPrefix202510\\generic\\sniffs\\namingconventions\\uppercaseconstantnamesniff' => \true, 'ECSPrefix202510\\generic\\sniffs\\php\\disallowalternativephptagssniff' => \true, 'ECSPrefix202510\\generic\\sniffs\\php\\disallowshortopentagsniff' => \true, 'ECSPrefix202510\\psr1\\sniffs\\classes\\classdeclarationsniff' => \true, 'ECSPrefix202510\\squiz\\sniffs\\classes\\validclassnamesniff' => \true];
        $exclusions = ['ECSPrefix202510\\squiz\\sniffs\\classes\\validclassnamesniff' => \true, 'ECSPrefix202510\\generic\\sniffs\\php\\disallowalternativephptagssniff' => \true, 'ECSPrefix202510\\generic\\sniffs\\namingconventions\\uppercaseconstantnamesniff' => \true];
        $expected = ['PHP_CodeSniffer\\Standards\\Generic\\Sniffs\\PHP\\DisallowShortOpenTagSniff', 'PHP_CodeSniffer\\Standards\\PSR1\\Sniffs\\Classes\\ClassDeclarationSniff'];
        self::$ruleset->registerSniffs(self::$psr1SniffAbsolutePaths, $restrictions, $exclusions);
        $this->assertSame($expected, \array_keys(self::$ruleset->sniffs));
    }
    //end testRegisteredSniffsBothRestrictionsAndExclusions()
    /**
     * Verify that abstract sniffs are filtered out and not registered.
     *
     * @return void
     */
    public function testRegisterSniffsFiltersOutAbstractClasses()
    {
        $extraPathsBaseDir = __DIR__ . \DIRECTORY_SEPARATOR . 'Fixtures' . \DIRECTORY_SEPARATOR;
        $extraPaths = ['DirectoryExpansion/.hiddenAbove/src/MyStandard/Sniffs/AbstractSniff.php', 'DirectoryExpansion/.hiddenAbove/src/MyStandard/Sniffs/CategoryB/AnotherAbstractSniff.php'];
        $extraPaths = self::relativeToAbsoluteSniffFiles($extraPathsBaseDir, $extraPaths);
        $fileList = self::$psr1SniffAbsolutePaths;
        foreach ($extraPaths as $path) {
            $fileList[] = $path;
        }
        self::$ruleset->registerSniffs($fileList, [], []);
        // Make sure the same sniff list was recreated (but without the objects having been created yet).
        $this->assertSame(\array_keys(self::$originalSniffs), \array_keys(self::$ruleset->sniffs));
        $this->assertSame(\array_keys(self::$originalSniffs), \array_values(self::$ruleset->sniffs));
    }
    //end testRegisterSniffsFiltersOutAbstractClasses()
    /**
     * Test that sniff files not in a "/Sniffs/" directory are filtered out and not registered.
     *
     * @return void
     */
    public function testRegisteredSniffsFiltersOutFilePathsWithoutSniffsDir()
    {
        $extraPathsBaseDir = __DIR__ . \DIRECTORY_SEPARATOR . 'Fixtures' . \DIRECTORY_SEPARATOR;
        $extraPaths = ['DirectoryExpansion/.hiddenAbove/src/MyStandard/Utils/NotInSniffsDirSniff.php', 'DirectoryExpansion/.hiddenAbove/src/MyStandard/Utils/SubDir/NotInSniffsDirSniff.php'];
        $extraPaths = self::relativeToAbsoluteSniffFiles($extraPathsBaseDir, $extraPaths);
        $fileList = self::$psr1SniffAbsolutePaths;
        foreach ($extraPaths as $path) {
            $fileList[] = $path;
        }
        self::$ruleset->registerSniffs($fileList, [], []);
        // Make sure the same sniff list was recreated (but without the objects having been created yet).
        $this->assertSame(\array_keys(self::$originalSniffs), \array_keys(self::$ruleset->sniffs));
        $this->assertSame(\array_keys(self::$originalSniffs), \array_values(self::$ruleset->sniffs));
    }
    //end testRegisteredSniffsFiltersOutFilePathsWithoutSniffsDir()
}
//end class
