<?php

/**
 * Tests the retokenization of the `exit`/`die` keywords to T_EXIT on PHP 8.4 and higher.
 *
 * @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\Tokenizers\PHP;

use PHP_CodeSniffer\Tests\Core\Tokenizers\AbstractTokenizerTestCase;
final class ExitKeywordTest extends AbstractTokenizerTestCase
{
    /**
     * Test the retokenization of the `exit`/`die` keywords to T_EXIT.
     *
     * @param string $testMarker  The comment prefacing the target token.
     * @param string $testContent The token content to look for.
     *
     * @dataProvider dataExitIsKeyword
     * @covers       PHP_CodeSniffer\Tokenizers\PHP::tokenize
     *
     * @return void
     */
    public function testExitIsKeyword($testMarker, $testContent)
    {
        $tokens = $this->phpcsFile->getTokens();
        $token = $this->getTargetToken($testMarker, [\T_EXIT, \T_STRING], $testContent);
        $tokenArray = $tokens[$token];
        $this->assertSame(\T_EXIT, $tokenArray['code'], 'Token tokenized as ' . $tokenArray['type'] . ', not T_EXIT (code)');
        $this->assertSame('T_EXIT', $tokenArray['type'], 'Token tokenized as ' . $tokenArray['type'] . ', not T_EXIT (type)');
    }
    //end testExitIsKeyword()
    /**
     * Data provider.
     *
     * @see testExitIsKeyword()
     *
     * @return array<string, array<string, string>>
     */
    public static function dataExitIsKeyword()
    {
        return ['exit as constant' => ['testMarker' => '/* testExitAsConstant */', 'testContent' => 'exit'], 'die as constant' => ['testMarker' => '/* testDieAsConstant */', 'testContent' => 'die'], 'exit as constant; mixed case' => ['testMarker' => '/* testExitAsConstantMixedCase */', 'testContent' => 'Exit'], 'die as constant; uppercase' => ['testMarker' => '/* testDieAsConstantUppercase */', 'testContent' => 'DIE'], 'exit as function call; no parameters' => ['testMarker' => '/* testExitAsFunctionCallNoParam */', 'testContent' => 'exit'], 'die as function call; no parameters' => ['testMarker' => '/* testDieAsFunctionCallNoParam */', 'testContent' => 'die'], 'exit as function call; with parameters' => ['testMarker' => '/* testExitAsFunctionCallWithParam */', 'testContent' => 'exit'], 'die as function call; with parameters' => ['testMarker' => '/* testDieAsFunctionCallWithParam */', 'testContent' => 'die'], 'exit as function call; uppercase' => ['testMarker' => '/* testExitAsFunctionCallUppercase */', 'testContent' => 'EXIT'], 'die as function call; mixed case' => ['testMarker' => '/* testDieAsFunctionCallMixedCase */', 'testContent' => 'dIE'], 'exit as fully qualified function call; with parameters' => ['testMarker' => '/* testExitAsFQFunctionCallWithParam */', 'testContent' => 'exit'], 'die as fully qualified function call; no parameters' => ['testMarker' => '/* testDieAsFQFunctionCallNoParam */', 'testContent' => 'die'], 'exit as fully qualified constant (illegal)' => ['testMarker' => '/* testExitAsFQConstant */', 'testContent' => 'exit'], 'die as fully qualified constant (illegal)' => ['testMarker' => '/* testDieAsFQConstant */', 'testContent' => 'die']];
    }
    //end dataExitIsKeyword()
    /**
     * Verify that the retokenization of `T_EXIT` tokens doesn't negatively impact the tokenization
     * of `T_STRING` tokens with the contents "exit" or "die" which aren't in actual fact the keyword.
     *
     * @param string $testMarker  The comment prefacing the target token.
     * @param string $testContent The token content to look for.
     * @param string $expected    The expected token type. Defaults to `T_STRING`.
     *
     * @dataProvider dataNotExitKeyword
     * @covers       PHP_CodeSniffer\Tokenizers\PHP::tokenize
     *
     * @return void
     */
    public function testNotExitKeyword($testMarker, $testContent, $expected = 'T_STRING')
    {
        $tokens = $this->phpcsFile->getTokens();
        $tokenCode = \constant($expected);
        $token = $this->getTargetToken($testMarker, [\T_EXIT, $tokenCode], $testContent);
        $tokenArray = $tokens[$token];
        $this->assertSame($tokenCode, $tokenArray['code'], 'Token tokenized as ' . $tokenArray['type'] . ', not ' . $expected . ' (code)');
        $this->assertSame($expected, $tokenArray['type'], 'Token tokenized as ' . $tokenArray['type'] . ', not ' . $expected . ' (type)');
    }
    //end testNotExitKeyword()
    /**
     * Data provider.
     *
     * @see testNotExitKeyword()
     *
     * @return array<string, array<string, string>>
     */
    public static function dataNotExitKeyword()
    {
        return ['exit not keyword: OO constant use' => ['testMarker' => '/* testNotExitOOConstantAccess */', 'testContent' => 'exit'], 'die not keyword: OO constant use' => ['testMarker' => '/* testNotDieOOConstantAccess */', 'testContent' => 'die'], 'exit not keyword: OO property access' => ['testMarker' => '/* testNotExitOOPropertyAccess */', 'testContent' => 'exit'], 'die not keyword: OO property access' => ['testMarker' => '/* testNotDieOOPropertyAccess */', 'testContent' => 'DIE'], 'exit not keyword: OO method call' => ['testMarker' => '/* testNotExitOOMethodCall */', 'testContent' => 'exit'], 'die not keyword: OO method call' => ['testMarker' => '/* testNotDieOOMethodCall */', 'testContent' => 'die'], 'exit not keyword: OO constant declaration' => ['testMarker' => '/* testNotExitOOConstDeclaration */', 'testContent' => 'exit'], 'die not keyword: OO constant declaration' => ['testMarker' => '/* testNotDieOOConstDeclaration */', 'testContent' => 'die'], 'exit not keyword: OO method declaration' => ['testMarker' => '/* testNotExitOOMethodDeclaration */', 'testContent' => 'Exit'], 'die not keyword: OO method declaration' => ['testMarker' => '/* testNotDieOOMethodDeclaration */', 'testContent' => 'die'], 'exit not keyword: parameter label for named param' => ['testMarker' => '/* testNotExitParamName */', 'testContent' => 'exit', 'expected' => 'T_PARAM_NAME'], 'die not keyword: parameter label for named param' => ['testMarker' => '/* testNotDieParamName */', 'testContent' => 'die', 'expected' => 'T_PARAM_NAME'], 'exit not keyword: part of a namespaced name' => ['testMarker' => '/* testNotExitNamespacedName */', 'testContent' => 'exit'], 'die not keyword: part of a namespaced name' => ['testMarker' => '/* testNotDieNamespacedName */', 'testContent' => 'die'], 'exit not keyword: global constant declaration (illegal)' => ['testMarker' => '/* testNotExitConstantDeclaration */', 'testContent' => 'exit'], 'die not keyword: global constant declaration (illegal)' => ['testMarker' => '/* testNotDieConstantDeclaration */', 'testContent' => 'die'], 'exit not keyword: global function declaration (illegal)' => ['testMarker' => '/* testNotExitFunctionDeclaration */', 'testContent' => 'exit'], 'die not keyword: global function declaration (illegal)' => ['testMarker' => '/* testNotDieFunctionDeclaration */', 'testContent' => 'die']];
    }
    //end dataNotExitKeyword()
}
//end class
