<?php
/**
 * Tests that simple tokens are assigned the correct token type and code.
 *
 * @author    Juliette Reinders Folmer <phpcs_nospam@adviesenzo.nl>
 * @copyright 2024 PHPCSStandards and contributors
 * @license   https://github.com/PHPCSStandards/PHP_CodeSniffer/blob/HEAD/licence.txt BSD Licence
 */

namespace PHP_CodeSniffer\Tests\Core\Tokenizers\PHP;

use PHP_CodeSniffer\Tests\Core\Tokenizers\AbstractTokenizerTestCase;
use PHP_CodeSniffer\Util\Tokens;

/**
 * Tests that simple tokens are assigned the correct token type and code.
 *
 * @covers PHP_CodeSniffer\Tokenizers\PHP::resolveSimpleToken
 */
final class ResolveSimpleTokenTest extends AbstractTokenizerTestCase
{


    /**
     * Clear the "resolved tokens" cache before running this test as otherwise the code
     * under test may not be run during the test.
     *
     * @beforeClass
     *
     * @return void
     */
    public static function clearTokenCache()
    {
        parent::clearResolvedTokensCache();

    }//end clearTokenCache()


    /**
     * Verify tokenization of parentheses, square brackets, curly brackets and a switch colon.
     *
     * @return void
     */
    public function testBracesAndColon()
    {
        $expectedSequence = [
            T_OPEN_PARENTHESIS,
            T_VARIABLE,
            T_OPEN_SQUARE_BRACKET,
            T_LNUMBER,
            T_CLOSE_SQUARE_BRACKET,
            T_CLOSE_PARENTHESIS,
            T_OPEN_CURLY_BRACKET,
            T_CASE,
            T_STRING,
            T_COLON,
            T_BREAK,
            T_SEMICOLON,
            T_CLOSE_CURLY_BRACKET,
        ];

        $target = $this->getTargetToken('/* '.__FUNCTION__.' */', T_SWITCH);

        $this->checkTokenSequence(($target + 1), $expectedSequence);

    }//end testBracesAndColon()


    /**
     * Verify tokenization of colon after named parameter.
     *
     * @return void
     */
    public function testNamedParamColon()
    {
        $expectedSequence = [
            T_OPEN_PARENTHESIS,
            T_PARAM_NAME,
            T_COLON,
            T_VARIABLE,
            T_CLOSE_PARENTHESIS,
            T_SEMICOLON,
        ];

        $target = $this->getTargetToken('/* '.__FUNCTION__.' */', T_STRING);

        $this->checkTokenSequence(($target + 1), $expectedSequence);

    }//end testNamedParamColon()


    /**
     * Verify tokenization of colon for a return type.
     *
     * @return void
     */
    public function testReturnTypeColon()
    {
        $expectedSequence = [
            T_EQUAL,
            T_CLOSURE,
            T_OPEN_PARENTHESIS,
            T_CLOSE_PARENTHESIS,
            T_COLON,
            T_STRING,
            T_OPEN_CURLY_BRACKET,
            T_CLOSE_CURLY_BRACKET,
            T_SEMICOLON,
        ];

        $target = $this->getTargetToken('/* '.__FUNCTION__.' */', T_VARIABLE);

        $this->checkTokenSequence(($target + 1), $expectedSequence);

    }//end testReturnTypeColon()


    /**
     * Verify tokenization of a concatenation operator.
     *
     * @return void
     */
    public function testConcat()
    {
        $expectedSequence = [
            T_STRING_CONCAT,
            T_VARIABLE,
            T_SEMICOLON,
        ];

        $target = $this->getTargetToken('/* '.__FUNCTION__.' */', T_CONSTANT_ENCAPSED_STRING);

        $this->checkTokenSequence(($target + 1), $expectedSequence);

    }//end testConcat()


    /**
     * Verify tokenization of simple math operators.
     *
     * @return void
     */
    public function testSimpleMathTokens()
    {
        $expectedSequence = [
            T_EQUAL,
            T_LNUMBER,
            T_MULTIPLY,
            T_LNUMBER,
            T_DIVIDE,
            T_LNUMBER,
            T_PLUS,
            T_LNUMBER,
            T_MINUS,
            T_LNUMBER,
            T_MODULUS,
            T_LNUMBER,
            T_SEMICOLON,
        ];

        $target = $this->getTargetToken('/* '.__FUNCTION__.' */', T_VARIABLE);

        $this->checkTokenSequence(($target + 1), $expectedSequence);

    }//end testSimpleMathTokens()


    /**
     * Verify tokenization of unary plus/minus operators.
     *
     * @return void
     */
    public function testUnaryPlusMinus()
    {
        $expectedSequence = [
            T_EQUAL,
            T_PLUS,
            T_LNUMBER,
            T_DIVIDE,
            T_MINUS,
            T_LNUMBER,
            T_SEMICOLON,
        ];

        $target = $this->getTargetToken('/* '.__FUNCTION__.' */', T_VARIABLE);

        $this->checkTokenSequence(($target + 1), $expectedSequence);

    }//end testUnaryPlusMinus()


    /**
     * Verify tokenization of bitwise operator tokens.
     *
     * @return void
     */
    public function testBitwiseTokens()
    {
        $expectedSequence = [
            T_EQUAL,
            T_STRING,
            T_BITWISE_XOR,
            T_STRING,
            T_BITWISE_AND,
            T_STRING,
            T_BITWISE_OR,
            T_STRING,
            T_BITWISE_NOT,
            T_STRING,
            T_SEMICOLON,
        ];

        $target = $this->getTargetToken('/* '.__FUNCTION__.' */', T_VARIABLE);

        $this->checkTokenSequence(($target + 1), $expectedSequence);

    }//end testBitwiseTokens()


    /**
     * Verify tokenization of bitwise operator tokens.
     *
     * @return void
     */
    public function testBitwiseOrInCatch()
    {
        $expectedSequence = [
            T_OPEN_PARENTHESIS,
            T_STRING,
            T_BITWISE_OR,
            T_STRING,
            T_VARIABLE,
            T_CLOSE_PARENTHESIS,
            T_OPEN_CURLY_BRACKET,
            T_CLOSE_CURLY_BRACKET,
        ];

        $target = $this->getTargetToken('/* '.__FUNCTION__.' */', T_CATCH);

        $this->checkTokenSequence(($target + 1), $expectedSequence);

    }//end testBitwiseOrInCatch()


    /**
     * Verify tokenization of a less than operator.
     *
     * @return void
     */
    public function testLessThan()
    {
        $expectedSequence = [
            T_LESS_THAN,
            T_VARIABLE,
            T_SEMICOLON,
        ];

        $target = $this->getTargetToken('/* '.__FUNCTION__.' */', T_LNUMBER);

        $this->checkTokenSequence(($target + 1), $expectedSequence);

    }//end testLessThan()


    /**
     * Verify tokenization of a greater than operator.
     *
     * @return void
     */
    public function testGreaterThan()
    {
        $expectedSequence = [
            T_GREATER_THAN,
            T_VARIABLE,
            T_SEMICOLON,
        ];

        $target = $this->getTargetToken('/* '.__FUNCTION__.' */', T_LNUMBER);

        $this->checkTokenSequence(($target + 1), $expectedSequence);

    }//end testGreaterThan()


    /**
     * Verify tokenization of a boolean not operator.
     *
     * @return void
     */
    public function testBooleanNot()
    {
        $expectedSequence = [
            T_BOOLEAN_NOT,
            T_VARIABLE,
            T_SEMICOLON,
        ];

        $target = $this->getTargetToken('/* '.__FUNCTION__.' */', T_EQUAL);

        $this->checkTokenSequence(($target + 1), $expectedSequence);

    }//end testBooleanNot()


    /**
     * Verify tokenization of commas.
     *
     * @return void
     */
    public function testComma()
    {
        $expectedSequence = [
            T_COMMA,
            T_VARIABLE,
            T_COMMA,
            T_VARIABLE,
            T_SEMICOLON,
        ];

        $target = $this->getTargetToken('/* '.__FUNCTION__.' */', T_VARIABLE);

        $this->checkTokenSequence(($target + 1), $expectedSequence);

    }//end testComma()


    /**
     * Verify tokenization of the silence operator.
     *
     * @return void
     */
    public function testAsperand()
    {
        $expectedSequence = [
            T_ASPERAND,
            T_STRING,
            T_OPEN_PARENTHESIS,
            T_CLOSE_PARENTHESIS,
            T_SEMICOLON,
        ];

        $target = $this->getTargetToken('/* '.__FUNCTION__.' */', T_EQUAL);

        $this->checkTokenSequence(($target + 1), $expectedSequence);

    }//end testAsperand()


    /**
     * Verify tokenization of the dollar token and curlies for a variable variable.
     *
     * @return void
     */
    public function testDollarAndCurlies()
    {
        $expectedSequence = [
            T_DOLLAR,
            T_OPEN_CURLY_BRACKET,
            T_VARIABLE,
            T_CLOSE_CURLY_BRACKET,
            T_SEMICOLON,
        ];

        $target = $this->getTargetToken('/* '.__FUNCTION__.' */', T_ECHO);

        $this->checkTokenSequence(($target + 1), $expectedSequence);

    }//end testDollarAndCurlies()


    /**
     * Verify tokenization of the backtick operator.
     *
     * @return void
     */
    public function testBacktick()
    {
        $expectedSequence = [
            T_BACKTICK,
            T_ENCAPSED_AND_WHITESPACE,
            T_BACKTICK,
            T_SEMICOLON,
        ];

        $target = $this->getTargetToken('/* '.__FUNCTION__.' */', T_EQUAL);

        $this->checkTokenSequence(($target + 1), $expectedSequence);

    }//end testBacktick()


    /**
     * Test helper. Check a token sequence complies with an expected token sequence.
     *
     * @param int               $startPtr         The position in the file to start checking from.
     * @param array<int|string> $expectedSequence The consecutive token constants to expect.
     *
     * @return void
     */
    private function checkTokenSequence($startPtr, array $expectedSequence)
    {
        $tokens = $this->phpcsFile->getTokens();

        $sequenceKey   = 0;
        $sequenceCount = count($expectedSequence);

        for ($i = $startPtr; $sequenceKey < $sequenceCount; $i++) {
            if (isset(Tokens::$emptyTokens[$tokens[$i]['code']]) === true) {
                // Ignore whitespace and comments, not interested in the tokenization of those for these tests.
                continue;
            }

            $expectedTokenName = Tokens::tokenName($expectedSequence[$sequenceKey]);

            $this->assertSame(
                $expectedSequence[$sequenceKey],
                $tokens[$i]['code'],
                'Token tokenized as '.Tokens::tokenName($tokens[$i]['code']).', not '.$expectedTokenName.' (code)'
            );

            $this->assertSame(
                $expectedTokenName,
                $tokens[$i]['type'],
                'Token tokenized as '.$tokens[$i]['type'].', not '.$expectedTokenName.' (type)'
            );

            ++$sequenceKey;
        }//end for

    }//end checkTokenSequence()


}//end class
