<?php
/**
 * PHPCSUtils, utility functions and classes for PHP_CodeSniffer sniff developers.
 *
 * @package   PHPCSUtils
 * @copyright 2019-2020 PHPCSUtils Contributors
 * @license   https://opensource.org/licenses/LGPL-3.0 LGPL3
 * @link      https://github.com/PHPCSStandards/PHPCSUtils
 *
 * This class is imported from the PHP_CodeSniffer project.
 *
 * Copyright of the original code in this class as per the import:
 * @author    Greg Sherwood <gsherwood@squiz.net>
 * @author    Juliette Reinders Folmer <jrf@phpcodesniffer.info>
 *
 * With documentation contributions from:
 * @author    Diogo Oliveira de Melo <dmelo87@gmail.com>
 * @author    George Mponos <gmponos@gmail.com>
 *
 * @copyright 2010-2019 Squiz Pty Ltd (ABN 77 084 670 600)
 * @license   https://github.com/PHPCSStandards/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
 */

namespace PHPCSUtils\Tests\BackCompat\BCFile;

use PHPCSUtils\BackCompat\BCFile;
use PHPCSUtils\Tests\PolyfilledTestCase;

/**
 * Tests for the \PHPCSUtils\BackCompat\BCFile::getMethodParameters method.
 *
 * @covers \PHPCSUtils\BackCompat\BCFile::getMethodParameters
 *
 * @group functiondeclarations
 *
 * @since 1.0.0
 */
class GetMethodParametersTest extends PolyfilledTestCase
{

    /**
     * Test receiving an expected exception when a non function/use token is passed.
     *
     * @dataProvider dataUnexpectedTokenException
     *
     * @param string                       $commentString   The comment which preceeds the test.
     * @param int|string|array<int|string> $targetTokenType The token type to search for after $commentString.
     *
     * @return void
     */
    public function testUnexpectedTokenException($commentString, $targetTokenType)
    {
        $this->expectPhpcsException('$stackPtr must be of type T_FUNCTION or T_CLOSURE or T_USE or T_FN');

        $target = $this->getTargetToken($commentString, $targetTokenType);
        BCFile::getMethodParameters(self::$phpcsFile, $target);
    }

    /**
     * Data Provider.
     *
     * @see testUnexpectedTokenException() For the array format.
     *
     * @return array<string, array<string, int|string|array<int|string>>>
     */
    public static function dataUnexpectedTokenException()
    {
        return [
            'interface' => [
                'commentString'   => '/* testNotAFunction */',
                'targetTokenType' => T_INTERFACE,
            ],
            'function-call-fn-phpcs-3.5.3-3.5.4' => [
                'commentString'   => '/* testFunctionCallFnPHPCS353-354 */',
                'targetTokenType' => [T_FN, T_STRING],
            ],
            'fn-live-coding' => [
                'commentString'   => '/* testArrowFunctionLiveCoding */',
                'targetTokenType' => [T_FN, T_STRING],
            ],
        ];
    }

    /**
     * Test receiving an expected exception when a non-closure use token is passed.
     *
     * @dataProvider dataInvalidUse
     *
     * @param string $identifier The comment which preceeds the test.
     *
     * @return void
     */
    public function testInvalidUse($identifier)
    {
        $this->expectPhpcsException('$stackPtr was not a valid T_USE');

        $use = $this->getTargetToken($identifier, [T_USE]);
        BCFile::getMethodParameters(self::$phpcsFile, $use);
    }

    /**
     * Data Provider.
     *
     * @see testInvalidUse() For the array format.
     *
     * @return array<string, array<string>>
     */
    public static function dataInvalidUse()
    {
        return [
            'ImportUse'      => ['/* testImportUse */'],
            'ImportGroupUse' => ['/* testImportGroupUse */'],
            'TraitUse'       => ['/* testTraitUse */'],
            'InvalidUse'     => ['/* testInvalidUse */'],
        ];
    }

    /**
     * Test receiving an empty array when there are no parameters.
     *
     * @dataProvider dataNoParams
     *
     * @param string                       $commentString   The comment which preceeds the test.
     * @param int|string|array<int|string> $targetTokenType Optional. The token type to search for after $commentString.
     *                                                      Defaults to the function/closure/arrow tokens.
     *
     * @return void
     */
    public function testNoParams($commentString, $targetTokenType = [T_FUNCTION, T_CLOSURE, T_FN])
    {
        $target = $this->getTargetToken($commentString, $targetTokenType);
        $result = BCFile::getMethodParameters(self::$phpcsFile, $target);

        $this->assertSame([], $result);
    }

    /**
     * Data Provider.
     *
     * @see testNoParams() For the array format.
     *
     * @return array<string, array<int|string|array<int|string>>>
     */
    public static function dataNoParams()
    {
        return [
            'FunctionNoParams'   => ['/* testFunctionNoParams */'],
            'ClosureNoParams'    => ['/* testClosureNoParams */'],
            'ClosureUseNoParams' => ['/* testClosureUseNoParams */', T_USE],
        ];
    }

    /**
     * Verify pass-by-reference parsing.
     *
     * @return void
     */
    public function testPassByReference()
    {
        // Offsets are relative to the T_FUNCTION token.
        $expected    = [];
        $expected[0] = [
            'token'               => 5,
            'name'                => '$var',
            'content'             => '&$var',
            'has_attributes'      => false,
            'pass_by_reference'   => true,
            'reference_token'     => 4,
            'variable_length'     => false,
            'variadic_token'      => false,
            'type_hint'           => '',
            'type_hint_token'     => false,
            'type_hint_end_token' => false,
            'nullable_type'       => false,
            'comma_token'         => false,
        ];

        $this->getMethodParametersTestHelper('/* ' . __FUNCTION__ . ' */', $expected);
    }

    /**
     * Verify array hint parsing.
     *
     * @return void
     */
    public function testArrayHint()
    {
        // Offsets are relative to the T_FUNCTION token.
        $expected    = [];
        $expected[0] = [
            'token'               => 6,
            'name'                => '$var',
            'content'             => 'array $var',
            'has_attributes'      => false,
            'pass_by_reference'   => false,
            'reference_token'     => false,
            'variable_length'     => false,
            'variadic_token'      => false,
            'type_hint'           => 'array',
            'type_hint_token'     => 4,
            'type_hint_end_token' => 4,
            'nullable_type'       => false,
            'comma_token'         => false,
        ];

        $this->getMethodParametersTestHelper('/* ' . __FUNCTION__ . ' */', $expected);
    }

    /**
     * Verify variable.
     *
     * @return void
     */
    public function testVariable()
    {
        // Offsets are relative to the T_FUNCTION token.
        $expected    = [];
        $expected[0] = [
            'token'               => 4,
            'name'                => '$var',
            'content'             => '$var',
            'has_attributes'      => false,
            'pass_by_reference'   => false,
            'reference_token'     => false,
            'variable_length'     => false,
            'variadic_token'      => false,
            'type_hint'           => '',
            'type_hint_token'     => false,
            'type_hint_end_token' => false,
            'nullable_type'       => false,
            'comma_token'         => false,
        ];

        $this->getMethodParametersTestHelper('/* ' . __FUNCTION__ . ' */', $expected);
    }

    /**
     * Verify default value parsing with a single function param.
     *
     * @return void
     */
    public function testSingleDefaultValue()
    {
        // Offsets are relative to the T_FUNCTION token.
        $expected    = [];
        $expected[0] = [
            'token'               => 4,
            'name'                => '$var1',
            'content'             => '$var1=self::CONSTANT',
            'default'             => 'self::CONSTANT',
            'default_token'       => 6,
            'default_equal_token' => 5,
            'has_attributes'      => false,
            'pass_by_reference'   => false,
            'reference_token'     => false,
            'variable_length'     => false,
            'variadic_token'      => false,
            'type_hint'           => '',
            'type_hint_token'     => false,
            'type_hint_end_token' => false,
            'nullable_type'       => false,
            'comma_token'         => false,
        ];

        $this->getMethodParametersTestHelper('/* ' . __FUNCTION__ . ' */', $expected);
    }

    /**
     * Verify default value parsing.
     *
     * @return void
     */
    public function testDefaultValues()
    {
        // Offsets are relative to the T_FUNCTION token.
        $expected    = [];
        $expected[0] = [
            'token'               => 4,
            'name'                => '$var1',
            'content'             => '$var1=1',
            'default'             => '1',
            'default_token'       => 6,
            'default_equal_token' => 5,
            'has_attributes'      => false,
            'pass_by_reference'   => false,
            'reference_token'     => false,
            'variable_length'     => false,
            'variadic_token'      => false,
            'type_hint'           => '',
            'type_hint_token'     => false,
            'type_hint_end_token' => false,
            'nullable_type'       => false,
            'comma_token'         => 7,
        ];
        $expected[1] = [
            'token'               => 9,
            'name'                => '$var2',
            'content'             => "\$var2='value'",
            'default'             => "'value'",
            'default_token'       => 11,
            'default_equal_token' => 10,
            'has_attributes'      => false,
            'pass_by_reference'   => false,
            'reference_token'     => false,
            'variable_length'     => false,
            'variadic_token'      => false,
            'type_hint'           => '',
            'type_hint_token'     => false,
            'type_hint_end_token' => false,
            'nullable_type'       => false,
            'comma_token'         => false,
        ];

        $this->getMethodParametersTestHelper('/* ' . __FUNCTION__ . ' */', $expected);
    }

    /**
     * Verify type hint parsing.
     *
     * @return void
     */
    public function testTypeHint()
    {
        // Offsets are relative to the T_FUNCTION token.
        $expected    = [];
        $expected[0] = [
            'token'               => 6,
            'name'                => '$var1',
            'content'             => 'foo $var1',
            'has_attributes'      => false,
            'pass_by_reference'   => false,
            'reference_token'     => false,
            'variable_length'     => false,
            'variadic_token'      => false,
            'type_hint'           => 'foo',
            'type_hint_token'     => 4,
            'type_hint_end_token' => 4,
            'nullable_type'       => false,
            'comma_token'         => 7,
        ];

        $expected[1] = [
            'token'               => 11,
            'name'                => '$var2',
            'content'             => 'bar $var2',
            'has_attributes'      => false,
            'pass_by_reference'   => false,
            'reference_token'     => false,
            'variable_length'     => false,
            'variadic_token'      => false,
            'type_hint'           => 'bar',
            'type_hint_token'     => 9,
            'type_hint_end_token' => 9,
            'nullable_type'       => false,
            'comma_token'         => false,
        ];

        $this->getMethodParametersTestHelper('/* ' . __FUNCTION__ . ' */', $expected);
    }

    /**
     * Verify self type hint parsing.
     *
     * @return void
     */
    public function testSelfTypeHint()
    {
        // Offsets are relative to the T_FUNCTION token.
        $expected    = [];
        $expected[0] = [
            'token'               => 6,
            'name'                => '$var',
            'content'             => 'self $var',
            'has_attributes'      => false,
            'pass_by_reference'   => false,
            'reference_token'     => false,
            'variable_length'     => false,
            'variadic_token'      => false,
            'type_hint'           => 'self',
            'type_hint_token'     => 4,
            'type_hint_end_token' => 4,
            'nullable_type'       => false,
            'comma_token'         => false,
        ];

        $this->getMethodParametersTestHelper('/* ' . __FUNCTION__ . ' */', $expected);
    }

    /**
     * Verify nullable type hint parsing.
     *
     * @return void
     */
    public function testNullableTypeHint()
    {
        $php8Names = parent::usesPhp8NameTokens();

        // Offsets are relative to the T_FUNCTION token.
        $expected    = [];
        $expected[0] = [
            'token'               => 7,
            'name'                => '$var1',
            'content'             => '?int $var1',
            'has_attributes'      => false,
            'pass_by_reference'   => false,
            'reference_token'     => false,
            'variable_length'     => false,
            'variadic_token'      => false,
            'type_hint'           => '?int',
            'type_hint_token'     => 5,
            'type_hint_end_token' => 5,
            'nullable_type'       => true,
            'comma_token'         => 8,
        ];

        $expected[1] = [
            'token'               => ($php8Names === true) ? 13 : 14,
            'name'                => '$var2',
            'content'             => '?\bar $var2',
            'has_attributes'      => false,
            'pass_by_reference'   => false,
            'reference_token'     => false,
            'variable_length'     => false,
            'variadic_token'      => false,
            'type_hint'           => '?\bar',
            'type_hint_token'     => 11,
            'type_hint_end_token' => ($php8Names === true) ? 11 : 12,
            'nullable_type'       => true,
            'comma_token'         => false,
        ];

        $this->getMethodParametersTestHelper('/* ' . __FUNCTION__ . ' */', $expected);
    }

    /**
     * Verify "bitwise and" in default value !== pass-by-reference.
     *
     * @return void
     */
    public function testBitwiseAndConstantExpressionDefaultValue()
    {
        // Offsets are relative to the T_FUNCTION token.
        $expected    = [];
        $expected[0] = [
            'token'               => 4,
            'name'                => '$a',
            'content'             => '$a = 10 & 20',
            'default'             => '10 & 20',
            'default_token'       => 8,
            'default_equal_token' => 6,
            'has_attributes'      => false,
            'pass_by_reference'   => false,
            'reference_token'     => false,
            'variable_length'     => false,
            'variadic_token'      => false,
            'type_hint'           => '',
            'type_hint_token'     => false,
            'type_hint_end_token' => false,
            'nullable_type'       => false,
            'comma_token'         => false,
        ];

        $this->getMethodParametersTestHelper('/* ' . __FUNCTION__ . ' */', $expected);
    }

    /**
     * Verify that arrow functions are supported.
     *
     * @return void
     */
    public function testArrowFunction()
    {
        // Offsets are relative to the T_FN token.
        $expected    = [];
        $expected[0] = [
            'token'               => 4,
            'name'                => '$a',
            'content'             => 'int $a',
            'has_attributes'      => false,
            'pass_by_reference'   => false,
            'reference_token'     => false,
            'variable_length'     => false,
            'variadic_token'      => false,
            'type_hint'           => 'int',
            'type_hint_token'     => 2,
            'type_hint_end_token' => 2,
            'nullable_type'       => false,
            'comma_token'         => 5,
        ];

        $expected[1] = [
            'token'               => 8,
            'name'                => '$b',
            'content'             => '...$b',
            'has_attributes'      => false,
            'pass_by_reference'   => false,
            'reference_token'     => false,
            'variable_length'     => true,
            'variadic_token'      => 7,
            'type_hint'           => '',
            'type_hint_token'     => false,
            'type_hint_end_token' => false,
            'nullable_type'       => false,
            'comma_token'         => false,
        ];

        $this->getMethodParametersTestHelper('/* ' . __FUNCTION__ . ' */', $expected);
    }

    /**
     * Verify that arrow functions are supported.
     *
     * @return void
     */
    public function testArrowFunctionReturnByRef()
    {
        // Offsets are relative to the T_FN token.
        $expected    = [];
        $expected[0] = [
            'token'               => 6,
            'name'                => '$a',
            'content'             => '?string $a',
            'has_attributes'      => false,
            'pass_by_reference'   => false,
            'reference_token'     => false,
            'variable_length'     => false,
            'variadic_token'      => false,
            'type_hint'           => '?string',
            'type_hint_token'     => 4,
            'type_hint_end_token' => 4,
            'nullable_type'       => true,
            'comma_token'         => false,
        ];

        $this->getMethodParametersTestHelper('/* ' . __FUNCTION__ . ' */', $expected);
    }

    /**
     * Verify default value parsing with array values.
     *
     * @return void
     */
    public function testArrayDefaultValues()
    {
        // Offsets are relative to the T_FUNCTION token.
        $expected    = [];
        $expected[0] = [
            'token'               => 4,
            'name'                => '$var1',
            'content'             => '$var1 = []',
            'default'             => '[]',
            'default_token'       => 8,
            'default_equal_token' => 6,
            'has_attributes'      => false,
            'pass_by_reference'   => false,
            'reference_token'     => false,
            'variable_length'     => false,
            'variadic_token'      => false,
            'type_hint'           => '',
            'type_hint_token'     => false,
            'type_hint_end_token' => false,
            'nullable_type'       => false,
            'comma_token'         => 10,
        ];
        $expected[1] = [
            'token'               => 12,
            'name'                => '$var2',
            'content'             => '$var2 = array(1, 2, 3)',
            'default'             => 'array(1, 2, 3)',
            'default_token'       => 16,
            'default_equal_token' => 14,
            'has_attributes'      => false,
            'pass_by_reference'   => false,
            'reference_token'     => false,
            'variable_length'     => false,
            'variadic_token'      => false,
            'type_hint'           => '',
            'type_hint_token'     => false,
            'type_hint_end_token' => false,
            'nullable_type'       => false,
            'comma_token'         => false,
        ];

        $this->getMethodParametersTestHelper('/* ' . __FUNCTION__ . ' */', $expected);
    }

    /**
     * Verify having a T_STRING constant as a default value for the second parameter.
     *
     * @return void
     */
    public function testConstantDefaultValueSecondParam()
    {
        // Offsets are relative to the T_FUNCTION token.
        $expected    = [];
        $expected[0] = [
            'token'               => 4,
            'name'                => '$var1',
            'content'             => '$var1',
            'has_attributes'      => false,
            'pass_by_reference'   => false,
            'reference_token'     => false,
            'variable_length'     => false,
            'variadic_token'      => false,
            'type_hint'           => '',
            'type_hint_token'     => false,
            'type_hint_end_token' => false,
            'nullable_type'       => false,
            'comma_token'         => 5,
        ];
        $expected[1] = [
            'token'               => 7,
            'name'                => '$var2',
            'content'             => '$var2 = M_PI',
            'default'             => 'M_PI',
            'default_token'       => 11,
            'default_equal_token' => 9,
            'has_attributes'      => false,
            'pass_by_reference'   => false,
            'reference_token'     => false,
            'variable_length'     => false,
            'variadic_token'      => false,
            'type_hint'           => '',
            'type_hint_token'     => false,
            'type_hint_end_token' => false,
            'nullable_type'       => false,
            'comma_token'         => false,
        ];

        $this->getMethodParametersTestHelper('/* ' . __FUNCTION__ . ' */', $expected);
    }

    /**
     * Verify distinquishing between a nullable type and a ternary within a default expression.
     *
     * @return void
     */
    public function testScalarTernaryExpressionInDefault()
    {
        // Offsets are relative to the T_FUNCTION token.
        $expected    = [];
        $expected[0] = [
            'token'               => 5,
            'name'                => '$a',
            'content'             => '$a = FOO ? \'bar\' : 10',
            'default'             => 'FOO ? \'bar\' : 10',
            'default_token'       => 9,
            'default_equal_token' => 7,
            'has_attributes'      => false,
            'pass_by_reference'   => false,
            'reference_token'     => false,
            'variable_length'     => false,
            'variadic_token'      => false,
            'type_hint'           => '',
            'type_hint_token'     => false,
            'type_hint_end_token' => false,
            'nullable_type'       => false,
            'comma_token'         => 18,
        ];
        $expected[1] = [
            'token'               => 24,
            'name'                => '$b',
            'content'             => '? bool $b',
            'has_attributes'      => false,
            'pass_by_reference'   => false,
            'reference_token'     => false,
            'variable_length'     => false,
            'variadic_token'      => false,
            'type_hint'           => '?bool',
            'type_hint_token'     => 22,
            'type_hint_end_token' => 22,
            'nullable_type'       => true,
            'comma_token'         => false,
        ];

        $this->getMethodParametersTestHelper('/* ' . __FUNCTION__ . ' */', $expected);
    }

    /**
     * Verify a variadic parameter being recognized correctly.
     *
     * @return void
     */
    public function testVariadicFunction()
    {
        // Offsets are relative to the T_FUNCTION token.
        $expected    = [];
        $expected[0] = [
            'token'               => 9,
            'name'                => '$a',
            'content'             => 'int ... $a',
            'has_attributes'      => false,
            'pass_by_reference'   => false,
            'reference_token'     => false,
            'variable_length'     => true,
            'variadic_token'      => 7,
            'type_hint'           => 'int',
            'type_hint_token'     => 5,
            'type_hint_end_token' => 5,
            'nullable_type'       => false,
            'comma_token'         => false,
        ];

        $this->getMethodParametersTestHelper('/* ' . __FUNCTION__ . ' */', $expected);
    }

    /**
     * Verify a variadic parameter passed by reference being recognized correctly.
     *
     * @return void
     */
    public function testVariadicByRefFunction()
    {
        // Offsets are relative to the T_FUNCTION token.
        $expected    = [];
        $expected[0] = [
            'token'               => 7,
            'name'                => '$a',
            'content'             => '&...$a',
            'has_attributes'      => false,
            'pass_by_reference'   => true,
            'reference_token'     => 5,
            'variable_length'     => true,
            'variadic_token'      => 6,
            'type_hint'           => '',
            'type_hint_token'     => false,
            'type_hint_end_token' => false,
            'nullable_type'       => false,
            'comma_token'         => false,
        ];

        $this->getMethodParametersTestHelper('/* ' . __FUNCTION__ . ' */', $expected);
    }

    /**
     * Verify handling of a variadic parameter with a class based type declaration.
     *
     * @return void
     */
    public function testVariadicFunctionClassType()
    {
        // Offsets are relative to the T_FUNCTION token.
        $expected    = [];
        $expected[0] = [
            'token'               => 4,
            'name'                => '$unit',
            'content'             => '$unit',
            'has_attributes'      => false,
            'pass_by_reference'   => false,
            'reference_token'     => false,
            'variable_length'     => false,
            'variadic_token'      => false,
            'type_hint'           => '',
            'type_hint_token'     => false,
            'type_hint_end_token' => false,
            'nullable_type'       => false,
            'comma_token'         => 5,
        ];
        $expected[1] = [
            'token'               => 10,
            'name'                => '$intervals',
            'content'             => 'DateInterval ...$intervals',
            'has_attributes'      => false,
            'pass_by_reference'   => false,
            'reference_token'     => false,
            'variable_length'     => true,
            'variadic_token'      => 9,
            'type_hint'           => 'DateInterval',
            'type_hint_token'     => 7,
            'type_hint_end_token' => 7,
            'nullable_type'       => false,
            'comma_token'         => false,
        ];

        $this->getMethodParametersTestHelper('/* ' . __FUNCTION__ . ' */', $expected);
    }

    /**
     * Verify distinquishing between a nullable type and a ternary within a default expression.
     *
     * @return void
     */
    public function testNameSpacedTypeDeclaration()
    {
        $php8Names = parent::usesPhp8NameTokens();

        // Offsets are relative to the T_FUNCTION token.
        $expected    = [];
        $expected[0] = [
            'token'               => ($php8Names === true) ? 7 : 12,
            'name'                => '$a',
            'content'             => '\Package\Sub\ClassName $a',
            'has_attributes'      => false,
            'pass_by_reference'   => false,
            'reference_token'     => false,
            'variable_length'     => false,
            'variadic_token'      => false,
            'type_hint'           => '\Package\Sub\ClassName',
            'type_hint_token'     => 5,
            'type_hint_end_token' => ($php8Names === true) ? 5 : 10,
            'nullable_type'       => false,
            'comma_token'         => ($php8Names === true) ? 8 : 13,
        ];
        $expected[1] = [
            'token'               => ($php8Names === true) ? 13 : 20,
            'name'                => '$b',
            'content'             => '?Sub\AnotherClass $b',
            'has_attributes'      => false,
            'pass_by_reference'   => false,
            'reference_token'     => false,
            'variable_length'     => false,
            'variadic_token'      => false,
            'type_hint'           => '?Sub\AnotherClass',
            'type_hint_token'     => ($php8Names === true) ? 11 : 16,
            'type_hint_end_token' => ($php8Names === true) ? 11 : 18,
            'nullable_type'       => true,
            'comma_token'         => false,
        ];

        $this->getMethodParametersTestHelper('/* ' . __FUNCTION__ . ' */', $expected);
    }

    /**
     * Verify correctly recognizing all type declarations supported by PHP.
     *
     * @return void
     */
    public function testWithAllTypes()
    {
        // Offsets are relative to the T_FUNCTION token.
        $expected     = [];
        $expected[0]  = [
            'token'               => 9,
            'name'                => '$a',
            'content'             => '?ClassName $a',
            'has_attributes'      => false,
            'pass_by_reference'   => false,
            'reference_token'     => false,
            'variable_length'     => false,
            'variadic_token'      => false,
            'type_hint'           => '?ClassName',
            'type_hint_token'     => 7,
            'type_hint_end_token' => 7,
            'nullable_type'       => true,
            'comma_token'         => 10,
        ];
        $expected[1]  = [
            'token'               => 15,
            'name'                => '$b',
            'content'             => 'self $b',
            'has_attributes'      => false,
            'pass_by_reference'   => false,
            'reference_token'     => false,
            'variable_length'     => false,
            'variadic_token'      => false,
            'type_hint'           => 'self',
            'type_hint_token'     => 13,
            'type_hint_end_token' => 13,
            'nullable_type'       => false,
            'comma_token'         => 16,
        ];
        $expected[2]  = [
            'token'               => 21,
            'name'                => '$c',
            'content'             => 'parent $c',
            'has_attributes'      => false,
            'pass_by_reference'   => false,
            'reference_token'     => false,
            'variable_length'     => false,
            'variadic_token'      => false,
            'type_hint'           => 'parent',
            'type_hint_token'     => 19,
            'type_hint_end_token' => 19,
            'nullable_type'       => false,
            'comma_token'         => 22,
        ];
        $expected[3]  = [
            'token'               => 27,
            'name'                => '$d',
            'content'             => 'object $d',
            'has_attributes'      => false,
            'pass_by_reference'   => false,
            'reference_token'     => false,
            'variable_length'     => false,
            'variadic_token'      => false,
            'type_hint'           => 'object',
            'type_hint_token'     => 25,
            'type_hint_end_token' => 25,
            'nullable_type'       => false,
            'comma_token'         => 28,
        ];
        $expected[4]  = [
            'token'               => 34,
            'name'                => '$e',
            'content'             => '?int $e',
            'has_attributes'      => false,
            'pass_by_reference'   => false,
            'reference_token'     => false,
            'variable_length'     => false,
            'variadic_token'      => false,
            'type_hint'           => '?int',
            'type_hint_token'     => 32,
            'type_hint_end_token' => 32,
            'nullable_type'       => true,
            'comma_token'         => 35,
        ];
        $expected[5]  = [
            'token'               => 41,
            'name'                => '$f',
            'content'             => 'string &$f',
            'has_attributes'      => false,
            'pass_by_reference'   => true,
            'reference_token'     => 40,
            'variable_length'     => false,
            'variadic_token'      => false,
            'type_hint'           => 'string',
            'type_hint_token'     => 38,
            'type_hint_end_token' => 38,
            'nullable_type'       => false,
            'comma_token'         => 42,
        ];
        $expected[6]  = [
            'token'               => 47,
            'name'                => '$g',
            'content'             => 'iterable $g',
            'has_attributes'      => false,
            'pass_by_reference'   => false,
            'reference_token'     => false,
            'variable_length'     => false,
            'variadic_token'      => false,
            'type_hint'           => 'iterable',
            'type_hint_token'     => 45,
            'type_hint_end_token' => 45,
            'nullable_type'       => false,
            'comma_token'         => 48,
        ];
        $expected[7]  = [
            'token'               => 53,
            'name'                => '$h',
            'content'             => 'bool $h = true',
            'default'             => 'true',
            'default_token'       => 57,
            'default_equal_token' => 55,
            'has_attributes'      => false,
            'pass_by_reference'   => false,
            'reference_token'     => false,
            'variable_length'     => false,
            'variadic_token'      => false,
            'type_hint'           => 'bool',
            'type_hint_token'     => 51,
            'type_hint_end_token' => 51,
            'nullable_type'       => false,
            'comma_token'         => 58,
        ];
        $expected[8]  = [
            'token'               => 63,
            'name'                => '$i',
            'content'             => 'callable $i = \'is_null\'',
            'default'             => "'is_null'",
            'default_token'       => 67,
            'default_equal_token' => 65,
            'has_attributes'      => false,
            'pass_by_reference'   => false,
            'reference_token'     => false,
            'variable_length'     => false,
            'variadic_token'      => false,
            'type_hint'           => 'callable',
            'type_hint_token'     => 61,
            'type_hint_end_token' => 61,
            'nullable_type'       => false,
            'comma_token'         => 68,
        ];
        $expected[9]  = [
            'token'               => 73,
            'name'                => '$j',
            'content'             => 'float $j = 1.1',
            'default'             => '1.1',
            'default_token'       => 77,
            'default_equal_token' => 75,
            'has_attributes'      => false,
            'pass_by_reference'   => false,
            'reference_token'     => false,
            'variable_length'     => false,
            'variadic_token'      => false,
            'type_hint'           => 'float',
            'type_hint_token'     => 71,
            'type_hint_end_token' => 71,
            'nullable_type'       => false,
            'comma_token'         => 78,
        ];
        $expected[10] = [
            'token'               => 84,
            'name'                => '$k',
            'content'             => 'array ...$k',
            'has_attributes'      => false,
            'pass_by_reference'   => false,
            'reference_token'     => false,
            'variable_length'     => true,
            'variadic_token'      => 83,
            'type_hint'           => 'array',
            'type_hint_token'     => 81,
            'type_hint_end_token' => 81,
            'nullable_type'       => false,
            'comma_token'         => false,
        ];

        $this->getMethodParametersTestHelper('/* ' . __FUNCTION__ . ' */', $expected);
    }

    /**
     * Verify correctly recognizing all type declarations supported by PHP when used with an arrow function.
     *
     * @return void
     */
    public function testArrowFunctionWithAllTypes()
    {
        // Offsets are relative to the T_FN token.
        $expected     = [];
        $expected[0]  = [
            'token'               => 7,
            'name'                => '$a',
            'content'             => '?ClassName $a',
            'has_attributes'      => false,
            'pass_by_reference'   => false,
            'reference_token'     => false,
            'variable_length'     => false,
            'variadic_token'      => false,
            'type_hint'           => '?ClassName',
            'type_hint_token'     => 5,
            'type_hint_end_token' => 5,
            'nullable_type'       => true,
            'comma_token'         => 8,
        ];
        $expected[1]  = [
            'token'               => 13,
            'name'                => '$b',
            'content'             => 'self $b',
            'has_attributes'      => false,
            'pass_by_reference'   => false,
            'reference_token'     => false,
            'variable_length'     => false,
            'variadic_token'      => false,
            'type_hint'           => 'self',
            'type_hint_token'     => 11,
            'type_hint_end_token' => 11,
            'nullable_type'       => false,
            'comma_token'         => 14,
        ];
        $expected[2]  = [
            'token'               => 19,
            'name'                => '$c',
            'content'             => 'parent $c',
            'has_attributes'      => false,
            'pass_by_reference'   => false,
            'reference_token'     => false,
            'variable_length'     => false,
            'variadic_token'      => false,
            'type_hint'           => 'parent',
            'type_hint_token'     => 17,
            'type_hint_end_token' => 17,
            'nullable_type'       => false,
            'comma_token'         => 20,
        ];
        $expected[3]  = [
            'token'               => 25,
            'name'                => '$d',
            'content'             => 'object $d',
            'has_attributes'      => false,
            'pass_by_reference'   => false,
            'reference_token'     => false,
            'variable_length'     => false,
            'variadic_token'      => false,
            'type_hint'           => 'object',
            'type_hint_token'     => 23,
            'type_hint_end_token' => 23,
            'nullable_type'       => false,
            'comma_token'         => 26,
        ];
        $expected[4]  = [
            'token'               => 32,
            'name'                => '$e',
            'content'             => '?int $e',
            'has_attributes'      => false,
            'pass_by_reference'   => false,
            'reference_token'     => false,
            'variable_length'     => false,
            'variadic_token'      => false,
            'type_hint'           => '?int',
            'type_hint_token'     => 30,
            'type_hint_end_token' => 30,
            'nullable_type'       => true,
            'comma_token'         => 33,
        ];
        $expected[5]  = [
            'token'               => 39,
            'name'                => '$f',
            'content'             => 'string &$f',
            'has_attributes'      => false,
            'pass_by_reference'   => true,
            'reference_token'     => 38,
            'variable_length'     => false,
            'variadic_token'      => false,
            'type_hint'           => 'string',
            'type_hint_token'     => 36,
            'type_hint_end_token' => 36,
            'nullable_type'       => false,
            'comma_token'         => 40,
        ];
        $expected[6]  = [
            'token'               => 45,
            'name'                => '$g',
            'content'             => 'iterable $g',
            'has_attributes'      => false,
            'pass_by_reference'   => false,
            'reference_token'     => false,
            'variable_length'     => false,
            'variadic_token'      => false,
            'type_hint'           => 'iterable',
            'type_hint_token'     => 43,
            'type_hint_end_token' => 43,
            'nullable_type'       => false,
            'comma_token'         => 46,
        ];
        $expected[7]  = [
            'token'               => 51,
            'name'                => '$h',
            'content'             => 'bool $h = true',
            'default'             => 'true',
            'default_token'       => 55,
            'default_equal_token' => 53,
            'has_attributes'      => false,
            'pass_by_reference'   => false,
            'reference_token'     => false,
            'variable_length'     => false,
            'variadic_token'      => false,
            'type_hint'           => 'bool',
            'type_hint_token'     => 49,
            'type_hint_end_token' => 49,
            'nullable_type'       => false,
            'comma_token'         => 56,
        ];
        $expected[8]  = [
            'token'               => 61,
            'name'                => '$i',
            'content'             => 'callable $i = \'is_null\'',
            'default'             => "'is_null'",
            'default_token'       => 65,
            'default_equal_token' => 63,
            'has_attributes'      => false,
            'pass_by_reference'   => false,
            'reference_token'     => false,
            'variable_length'     => false,
            'variadic_token'      => false,
            'type_hint'           => 'callable',
            'type_hint_token'     => 59,
            'type_hint_end_token' => 59,
            'nullable_type'       => false,
            'comma_token'         => 66,
        ];
        $expected[9]  = [
            'token'               => 71,
            'name'                => '$j',
            'content'             => 'float $j = 1.1',
            'default'             => '1.1',
            'default_token'       => 75,
            'default_equal_token' => 73,
            'has_attributes'      => false,
            'pass_by_reference'   => false,
            'reference_token'     => false,
            'variable_length'     => false,
            'variadic_token'      => false,
            'type_hint'           => 'float',
            'type_hint_token'     => 69,
            'type_hint_end_token' => 69,
            'nullable_type'       => false,
            'comma_token'         => 76,
        ];
        $expected[10] = [
            'token'               => 82,
            'name'                => '$k',
            'content'             => 'array ...$k',
            'has_attributes'      => false,
            'pass_by_reference'   => false,
            'reference_token'     => false,
            'variable_length'     => true,
            'variadic_token'      => 81,
            'type_hint'           => 'array',
            'type_hint_token'     => 79,
            'type_hint_end_token' => 79,
            'nullable_type'       => false,
            'comma_token'         => false,
        ];

        $this->getMethodParametersTestHelper('/* ' . __FUNCTION__ . ' */', $expected);
    }

    /**
     * Verify handling of a declaration interlaced with whitespace and comments.
     *
     * @return void
     */
    public function testMessyDeclaration()
    {
        $php8Names = parent::usesPhp8NameTokens();

        // Offsets are relative to the T_FUNCTION token.
        $expected    = [];
        $expected[0] = [
            'token'               => ($php8Names === true) ? 24 : 25,
            'name'                => '$a',
            'content'             => '// comment
    ?\MyNS /* comment */
        \ SubCat // phpcs:ignore Standard.Cat.Sniff -- for reasons.
            \  MyClass $a',
            'has_attributes'      => false,
            'pass_by_reference'   => false,
            'reference_token'     => false,
            'variable_length'     => false,
            'variadic_token'      => false,
            'type_hint'           => '?\MyNS\SubCat\MyClass',
            'type_hint_token'     => 9,
            'type_hint_end_token' => ($php8Names === true) ? 22 : 23,
            'nullable_type'       => true,
            'comma_token'         => ($php8Names === true) ? 25 : 26,
        ];
        $expected[1] = [
            'token'               => ($php8Names === true) ? 28 : 29,
            'name'                => '$b',
            'content'             => "\$b /* test */ = /* test */ 'default' /* test*/",
            'default'             => "'default' /* test*/",
            'default_token'       => ($php8Names === true) ? 36 : 37,
            'default_equal_token' => ($php8Names === true) ? 32 : 33,
            'has_attributes'      => false,
            'pass_by_reference'   => false,
            'reference_token'     => false,
            'variable_length'     => false,
            'variadic_token'      => false,
            'type_hint'           => '',
            'type_hint_token'     => false,
            'type_hint_end_token' => false,
            'nullable_type'       => false,
            'comma_token'         => ($php8Names === true) ? 39 : 40,
        ];
        $expected[2] = [
            'token'               => ($php8Names === true) ? 61 : 62,
            'name'                => '$c',
            'content'             => '// phpcs:ignore Stnd.Cat.Sniff -- For reasons.
    ? /*comment*/
        bool // phpcs:disable Stnd.Cat.Sniff -- For reasons.
        & /*test*/ ... /* phpcs:ignore */ $c',
            'has_attributes'      => false,
            'pass_by_reference'   => true,
            'reference_token'     => ($php8Names === true) ? 53 : 54,
            'variable_length'     => true,
            'variadic_token'      => ($php8Names === true) ? 57 : 58,
            'type_hint'           => '?bool',
            'type_hint_token'     => ($php8Names === true) ? 49 : 50,
            'type_hint_end_token' => ($php8Names === true) ? 49 : 50,
            'nullable_type'       => true,
            'comma_token'         => false,
        ];

        $this->getMethodParametersTestHelper('/* ' . __FUNCTION__ . ' */', $expected);
    }

    /**
     * Verify recognition of PHP8 mixed type declaration.
     *
     * @return void
     */
    public function testPHP8MixedTypeHint()
    {
        // Offsets are relative to the T_FUNCTION token.
        $expected    = [];
        $expected[0] = [
            'token'               => 8,
            'name'                => '$var1',
            'content'             => 'mixed &...$var1',
            'has_attributes'      => false,
            'pass_by_reference'   => true,
            'reference_token'     => 6,
            'variable_length'     => true,
            'variadic_token'      => 7,
            'type_hint'           => 'mixed',
            'type_hint_token'     => 4,
            'type_hint_end_token' => 4,
            'nullable_type'       => false,
            'comma_token'         => false,
        ];

        $this->getMethodParametersTestHelper('/* ' . __FUNCTION__ . ' */', $expected);
    }

    /**
     * Verify recognition of PHP8 mixed type declaration with nullability.
     *
     * @return void
     */
    public function testPHP8MixedTypeHintNullable()
    {
        // Offsets are relative to the T_FUNCTION token.
        $expected    = [];
        $expected[0] = [
            'token'               => 7,
            'name'                => '$var1',
            'content'             => '?Mixed $var1',
            'has_attributes'      => false,
            'pass_by_reference'   => false,
            'reference_token'     => false,
            'variable_length'     => false,
            'variadic_token'      => false,
            'type_hint'           => '?Mixed',
            'type_hint_token'     => 5,
            'type_hint_end_token' => 5,
            'nullable_type'       => true,
            'comma_token'         => false,
        ];

        $this->getMethodParametersTestHelper('/* ' . __FUNCTION__ . ' */', $expected);
    }

    /**
     * Verify recognition of type declarations using the namespace operator.
     *
     * @return void
     */
    public function testNamespaceOperatorTypeHint()
    {
        $php8Names = parent::usesPhp8NameTokens();

        // Offsets are relative to the T_FUNCTION token.
        $expected    = [];
        $expected[0] = [
            'token'               => ($php8Names === true) ? 7 : 9,
            'name'                => '$var1',
            'content'             => '?namespace\Name $var1',
            'has_attributes'      => false,
            'pass_by_reference'   => false,
            'reference_token'     => false,
            'variable_length'     => false,
            'variadic_token'      => false,
            'type_hint'           => '?namespace\Name',
            'type_hint_token'     => 5,
            'type_hint_end_token' => ($php8Names === true) ? 5 : 7,
            'nullable_type'       => true,
            'comma_token'         => false,
        ];

        $this->getMethodParametersTestHelper('/* ' . __FUNCTION__ . ' */', $expected);
    }

    /**
     * Verify recognition of PHP8 union type declaration.
     *
     * @return void
     */
    public function testPHP8UnionTypesSimple()
    {
        // Offsets are relative to the T_FUNCTION token.
        $expected    = [];
        $expected[0] = [
            'token'               => 8,
            'name'                => '$number',
            'content'             => 'int|float $number',
            'has_attributes'      => false,
            'pass_by_reference'   => false,
            'reference_token'     => false,
            'variable_length'     => false,
            'variadic_token'      => false,
            'type_hint'           => 'int|float',
            'type_hint_token'     => 4,
            'type_hint_end_token' => 6,
            'nullable_type'       => false,
            'comma_token'         => 9,
        ];
        $expected[1] = [
            'token'               => 17,
            'name'                => '$obj',
            'content'             => 'self|parent &...$obj',
            'has_attributes'      => false,
            'pass_by_reference'   => true,
            'reference_token'     => 15,
            'variable_length'     => true,
            'variadic_token'      => 16,
            'type_hint'           => 'self|parent',
            'type_hint_token'     => 11,
            'type_hint_end_token' => 13,
            'nullable_type'       => false,
            'comma_token'         => false,
        ];

        $this->getMethodParametersTestHelper('/* ' . __FUNCTION__ . ' */', $expected);
    }

    /**
     * Verify recognition of PHP8 union type declaration when the variable has either a spread operator or a reference.
     *
     * @return void
     */
    public function testPHP8UnionTypesWithSpreadOperatorAndReference()
    {
        // Offsets are relative to the T_FUNCTION token.
        $expected    = [];
        $expected[0] = [
            'token'               => 9,
            'name'                => '$paramA',
            'content'             => 'float|null &$paramA',
            'has_attributes'      => false,
            'pass_by_reference'   => true,
            'reference_token'     => 8,
            'variable_length'     => false,
            'variadic_token'      => false,
            'type_hint'           => 'float|null',
            'type_hint_token'     => 4,
            'type_hint_end_token' => 6,
            'nullable_type'       => false,
            'comma_token'         => 10,
        ];
        $expected[1] = [
            'token'               => 17,
            'name'                => '$paramB',
            'content'             => 'string|int ...$paramB',
            'has_attributes'      => false,
            'pass_by_reference'   => false,
            'reference_token'     => false,
            'variable_length'     => true,
            'variadic_token'      => 16,
            'type_hint'           => 'string|int',
            'type_hint_token'     => 12,
            'type_hint_end_token' => 14,
            'nullable_type'       => false,
            'comma_token'         => false,
        ];

        $this->getMethodParametersTestHelper('/* ' . __FUNCTION__ . ' */', $expected);
    }

    /**
     * Verify recognition of PHP8 union type declaration with a bitwise or in the default value.
     *
     * @return void
     */
    public function testPHP8UnionTypesSimpleWithBitwiseOrInDefault()
    {
        // Offsets are relative to the T_FUNCTION token.
        $expected    = [];
        $expected[0] = [
            'token'               => 6,
            'name'                => '$var',
            'content'             => 'int|float $var = CONSTANT_A | CONSTANT_B',
            'default'             => 'CONSTANT_A | CONSTANT_B',
            'default_token'       => 10,
            'default_equal_token' => 8,
            'has_attributes'      => false,
            'pass_by_reference'   => false,
            'reference_token'     => false,
            'variable_length'     => false,
            'variadic_token'      => false,
            'type_hint'           => 'int|float',
            'type_hint_token'     => 2,
            'type_hint_end_token' => 4,
            'nullable_type'       => false,
            'comma_token'         => false,
        ];

        $this->getMethodParametersTestHelper('/* ' . __FUNCTION__ . ' */', $expected);
    }

    /**
     * Verify recognition of PHP8 union type declaration with two classes.
     *
     * @return void
     */
    public function testPHP8UnionTypesTwoClasses()
    {
        $php8Names = parent::usesPhp8NameTokens();

        // Offsets are relative to the T_FUNCTION token.
        $expected    = [];
        $expected[0] = [
            'token'               => ($php8Names === true) ? 8 : 11,
            'name'                => '$var',
            'content'             => 'MyClassA|\Package\MyClassB $var',
            'has_attributes'      => false,
            'pass_by_reference'   => false,
            'reference_token'     => false,
            'variable_length'     => false,
            'variadic_token'      => false,
            'type_hint'           => 'MyClassA|\Package\MyClassB',
            'type_hint_token'     => 4,
            'type_hint_end_token' => ($php8Names === true) ? 6 : 9,
            'nullable_type'       => false,
            'comma_token'         => false,
        ];

        $this->getMethodParametersTestHelper('/* ' . __FUNCTION__ . ' */', $expected);
    }

    /**
     * Verify recognition of PHP8 union type declaration with all base types.
     *
     * @return void
     */
    public function testPHP8UnionTypesAllBaseTypes()
    {
        // Offsets are relative to the T_FUNCTION token.
        $expected    = [];
        $expected[0] = [
            'token'               => 20,
            'name'                => '$var',
            'content'             => 'array|bool|callable|int|float|null|object|string $var',
            'has_attributes'      => false,
            'pass_by_reference'   => false,
            'reference_token'     => false,
            'variable_length'     => false,
            'variadic_token'      => false,
            'type_hint'           => 'array|bool|callable|int|float|null|object|string',
            'type_hint_token'     => 4,
            'type_hint_end_token' => 18,
            'nullable_type'       => false,
            'comma_token'         => false,
        ];

        $this->getMethodParametersTestHelper('/* ' . __FUNCTION__ . ' */', $expected);
    }

    /**
     * Verify recognition of PHP8 union type declaration with all pseudo types.
     *
     * Note: "Resource" is not a type, but seen as a class name.
     *
     * @return void
     */
    public function testPHP8UnionTypesAllPseudoTypes()
    {
        // Offsets are relative to the T_FUNCTION token.
        $expected    = [];
        $expected[0] = [
            'token'               => 16,
            'name'                => '$var',
            'content'             => 'false|mixed|self|parent|iterable|Resource $var',
            'has_attributes'      => false,
            'pass_by_reference'   => false,
            'reference_token'     => false,
            'variable_length'     => false,
            'variadic_token'      => false,
            'type_hint'           => 'false|mixed|self|parent|iterable|Resource',
            'type_hint_token'     => 4,
            'type_hint_end_token' => 14,
            'nullable_type'       => false,
            'comma_token'         => false,
        ];

        $this->getMethodParametersTestHelper('/* ' . __FUNCTION__ . ' */', $expected);
    }

    /**
     * Verify recognition of PHP8 union type declaration with (illegal) nullability.
     *
     * @return void
     */
    public function testPHP8UnionTypesNullable()
    {
        // Offsets are relative to the T_FUNCTION token.
        $expected    = [];
        $expected[0] = [
            'token'               => 8,
            'name'                => '$number',
            'content'             => '?int|float $number',
            'has_attributes'      => false,
            'pass_by_reference'   => false,
            'reference_token'     => false,
            'variable_length'     => false,
            'variadic_token'      => false,
            'type_hint'           => '?int|float',
            'type_hint_token'     => 4,
            'type_hint_end_token' => 6,
            'nullable_type'       => true,
            'comma_token'         => false,
        ];

        $this->getMethodParametersTestHelper('/* ' . __FUNCTION__ . ' */', $expected);
    }

    /**
     * Verify recognition of PHP8 type declaration with (illegal) single type null.
     *
     * @return void
     */
    public function testPHP8PseudoTypeNull()
    {
        // Offsets are relative to the T_FUNCTION token.
        $expected    = [];
        $expected[0] = [
            'token'               => 6,
            'name'                => '$var',
            'content'             => 'null $var = null',
            'default'             => 'null',
            'default_token'       => 10,
            'default_equal_token' => 8,
            'has_attributes'      => false,
            'pass_by_reference'   => false,
            'reference_token'     => false,
            'variable_length'     => false,
            'variadic_token'      => false,
            'type_hint'           => 'null',
            'type_hint_token'     => 4,
            'type_hint_end_token' => 4,
            'nullable_type'       => false,
            'comma_token'         => false,
        ];

        $this->getMethodParametersTestHelper('/* ' . __FUNCTION__ . ' */', $expected);
    }

    /**
     * Verify recognition of PHP8 type declaration with (illegal) single type false.
     *
     * @return void
     */
    public function testPHP8PseudoTypeFalse()
    {
        // Offsets are relative to the T_FUNCTION token.
        $expected    = [];
        $expected[0] = [
            'token'               => 6,
            'name'                => '$var',
            'content'             => 'false $var = false',
            'default'             => 'false',
            'default_token'       => 10,
            'default_equal_token' => 8,
            'has_attributes'      => false,
            'pass_by_reference'   => false,
            'reference_token'     => false,
            'variable_length'     => false,
            'variadic_token'      => false,
            'type_hint'           => 'false',
            'type_hint_token'     => 4,
            'type_hint_end_token' => 4,
            'nullable_type'       => false,
            'comma_token'         => false,
        ];

        $this->getMethodParametersTestHelper('/* ' . __FUNCTION__ . ' */', $expected);
    }

    /**
     * Verify recognition of PHP8 type declaration with (illegal) type false combined with type bool.
     *
     * @return void
     */
    public function testPHP8PseudoTypeFalseAndBool()
    {
        // Offsets are relative to the T_FUNCTION token.
        $expected    = [];
        $expected[0] = [
            'token'               => 8,
            'name'                => '$var',
            'content'             => 'bool|false $var = false',
            'default'             => 'false',
            'default_token'       => 12,
            'default_equal_token' => 10,
            'has_attributes'      => false,
            'pass_by_reference'   => false,
            'reference_token'     => false,
            'variable_length'     => false,
            'variadic_token'      => false,
            'type_hint'           => 'bool|false',
            'type_hint_token'     => 4,
            'type_hint_end_token' => 6,
            'nullable_type'       => false,
            'comma_token'         => false,
        ];

        $this->getMethodParametersTestHelper('/* ' . __FUNCTION__ . ' */', $expected);
    }

    /**
     * Verify recognition of PHP8 type declaration with (illegal) type object combined with a class name.
     *
     * @return void
     */
    public function testPHP8ObjectAndClass()
    {
        // Offsets are relative to the T_FUNCTION token.
        $expected    = [];
        $expected[0] = [
            'token'               => 8,
            'name'                => '$var',
            'content'             => 'object|ClassName $var',
            'has_attributes'      => false,
            'pass_by_reference'   => false,
            'reference_token'     => false,
            'variable_length'     => false,
            'variadic_token'      => false,
            'type_hint'           => 'object|ClassName',
            'type_hint_token'     => 4,
            'type_hint_end_token' => 6,
            'nullable_type'       => false,
            'comma_token'         => false,
        ];

        $this->getMethodParametersTestHelper('/* ' . __FUNCTION__ . ' */', $expected);
    }

    /**
     * Verify recognition of PHP8 type declaration with (illegal) type iterable combined with array/Traversable.
     *
     * @return void
     */
    public function testPHP8PseudoTypeIterableAndArray()
    {
        // Offsets are relative to the T_FUNCTION token.
        $expected    = [];
        $expected[0] = [
            'token'               => 10,
            'name'                => '$var',
            'content'             => 'iterable|array|Traversable $var',
            'has_attributes'      => false,
            'pass_by_reference'   => false,
            'reference_token'     => false,
            'variable_length'     => false,
            'variadic_token'      => false,
            'type_hint'           => 'iterable|array|Traversable',
            'type_hint_token'     => 4,
            'type_hint_end_token' => 8,
            'nullable_type'       => false,
            'comma_token'         => false,
        ];

        $this->getMethodParametersTestHelper('/* ' . __FUNCTION__ . ' */', $expected);
    }

    /**
     * Verify recognition of PHP8 type declaration with (illegal) duplicate types.
     *
     * @return void
     */
    public function testPHP8DuplicateTypeInUnionWhitespaceAndComment()
    {
        // Offsets are relative to the T_FUNCTION token.
        $expected    = [];
        $expected[0] = [
            'token'               => 17,
            'name'                => '$var',
            'content'             => 'int | string /*comment*/ | INT $var',
            'has_attributes'      => false,
            'pass_by_reference'   => false,
            'reference_token'     => false,
            'variable_length'     => false,
            'variadic_token'      => false,
            'type_hint'           => 'int|string|INT',
            'type_hint_token'     => 5,
            'type_hint_end_token' => 15,
            'nullable_type'       => false,
            'comma_token'         => false,
        ];

        $this->getMethodParametersTestHelper('/* ' . __FUNCTION__ . ' */', $expected);
    }

    /**
     * Verify recognition of PHP8 constructor property promotion without type declaration, with defaults.
     *
     * @return void
     */
    public function testPHP8ConstructorPropertyPromotionNoTypes()
    {
        // Offsets are relative to the T_FUNCTION token.
        $expected    = [];
        $expected[0] = [
            'token'               => 8,
            'name'                => '$x',
            'content'             => 'public $x = 0.0',
            'default'             => '0.0',
            'default_token'       => 12,
            'default_equal_token' => 10,
            'has_attributes'      => false,
            'pass_by_reference'   => false,
            'reference_token'     => false,
            'variable_length'     => false,
            'variadic_token'      => false,
            'type_hint'           => '',
            'type_hint_token'     => false,
            'type_hint_end_token' => false,
            'nullable_type'       => false,
            'property_visibility' => 'public',
            'visibility_token'    => 6,
            'property_readonly'   => false,
            'comma_token'         => 13,
        ];
        $expected[1] = [
            'token'               => 18,
            'name'                => '$y',
            'content'             => 'protected $y = \'\'',
            'default'             => "''",
            'default_token'       => 22,
            'default_equal_token' => 20,
            'has_attributes'      => false,
            'pass_by_reference'   => false,
            'reference_token'     => false,
            'variable_length'     => false,
            'variadic_token'      => false,
            'type_hint'           => '',
            'type_hint_token'     => false,
            'type_hint_end_token' => false,
            'nullable_type'       => false,
            'property_visibility' => 'protected',
            'visibility_token'    => 16,
            'property_readonly'   => false,
            'comma_token'         => 23,
        ];
        $expected[2] = [
            'token'               => 28,
            'name'                => '$z',
            'content'             => 'private $z = null',
            'default'             => 'null',
            'default_token'       => 32,
            'default_equal_token' => 30,
            'has_attributes'      => false,
            'pass_by_reference'   => false,
            'reference_token'     => false,
            'variable_length'     => false,
            'variadic_token'      => false,
            'type_hint'           => '',
            'type_hint_token'     => false,
            'type_hint_end_token' => false,
            'nullable_type'       => false,
            'property_visibility' => 'private',
            'visibility_token'    => 26,
            'property_readonly'   => false,
            'comma_token'         => 33,
        ];

        $this->getMethodParametersTestHelper('/* ' . __FUNCTION__ . ' */', $expected);
    }

    /**
     * Verify recognition of PHP8 constructor property promotion with type declarations.
     *
     * @return void
     */
    public function testPHP8ConstructorPropertyPromotionWithTypes()
    {
        // Offsets are relative to the T_FUNCTION token.
        $expected    = [];
        $expected[0] = [
            'token'               => 10,
            'name'                => '$x',
            'content'             => 'protected float|int $x',
            'has_attributes'      => false,
            'pass_by_reference'   => false,
            'reference_token'     => false,
            'variable_length'     => false,
            'variadic_token'      => false,
            'type_hint'           => 'float|int',
            'type_hint_token'     => 6,
            'type_hint_end_token' => 8,
            'nullable_type'       => false,
            'property_visibility' => 'protected',
            'visibility_token'    => 4,
            'property_readonly'   => false,
            'comma_token'         => 11,
        ];
        $expected[1] = [
            'token'               => 19,
            'name'                => '$y',
            'content'             => 'public ?string &$y = \'test\'',
            'default'             => "'test'",
            'default_token'       => 23,
            'default_equal_token' => 21,
            'has_attributes'      => false,
            'pass_by_reference'   => true,
            'reference_token'     => 18,
            'variable_length'     => false,
            'variadic_token'      => false,
            'type_hint'           => '?string',
            'type_hint_token'     => 16,
            'type_hint_end_token' => 16,
            'nullable_type'       => true,
            'property_visibility' => 'public',
            'visibility_token'    => 13,
            'property_readonly'   => false,
            'comma_token'         => 24,
        ];
        $expected[2] = [
            'token'               => 30,
            'name'                => '$z',
            'content'             => 'private mixed $z',
            'has_attributes'      => false,
            'pass_by_reference'   => false,
            'reference_token'     => false,
            'variable_length'     => false,
            'variadic_token'      => false,
            'type_hint'           => 'mixed',
            'type_hint_token'     => 28,
            'type_hint_end_token' => 28,
            'nullable_type'       => false,
            'property_visibility' => 'private',
            'visibility_token'    => 26,
            'property_readonly'   => false,
            'comma_token'         => false,
        ];

        $this->getMethodParametersTestHelper('/* ' . __FUNCTION__ . ' */', $expected);
    }

    /**
     * Verify recognition of PHP8 constructor with both property promotion as well as normal parameters.
     *
     * @return void
     */
    public function testPHP8ConstructorPropertyPromotionAndNormalParam()
    {
        // Offsets are relative to the T_FUNCTION token.
        $expected    = [];
        $expected[0] = [
            'token'               => 8,
            'name'                => '$promotedProp',
            'content'             => 'public int $promotedProp',
            'has_attributes'      => false,
            'pass_by_reference'   => false,
            'reference_token'     => false,
            'variable_length'     => false,
            'variadic_token'      => false,
            'type_hint'           => 'int',
            'type_hint_token'     => 6,
            'type_hint_end_token' => 6,
            'nullable_type'       => false,
            'property_visibility' => 'public',
            'visibility_token'    => 4,
            'property_readonly'   => false,
            'comma_token'         => 9,
        ];
        $expected[1] = [
            'token'               => 14,
            'name'                => '$normalArg',
            'content'             => '?int $normalArg',
            'has_attributes'      => false,
            'pass_by_reference'   => false,
            'reference_token'     => false,
            'variable_length'     => false,
            'variadic_token'      => false,
            'type_hint'           => '?int',
            'type_hint_token'     => 12,
            'type_hint_end_token' => 12,
            'nullable_type'       => true,
            'comma_token'         => false,
        ];

        $this->getMethodParametersTestHelper('/* ' . __FUNCTION__ . ' */', $expected);
    }

    /**
     * Verify recognition of PHP8 constructor with property promotion using PHP 8.1 readonly keyword.
     *
     * @return void
     */
    public function testPHP81ConstructorPropertyPromotionWithReadOnly()
    {
        // Offsets are relative to the T_FUNCTION token.
        $expected    = [];
        $expected[0] = [
            'token'               => 11,
            'name'                => '$promotedProp',
            'content'             => 'public readonly ?int $promotedProp',
            'has_attributes'      => false,
            'pass_by_reference'   => false,
            'reference_token'     => false,
            'variable_length'     => false,
            'variadic_token'      => false,
            'type_hint'           => '?int',
            'type_hint_token'     => 9,
            'type_hint_end_token' => 9,
            'nullable_type'       => true,
            'property_visibility' => 'public',
            'visibility_token'    => 4,
            'property_readonly'   => true,
            'readonly_token'      => 6,
            'comma_token'         => 12,
        ];
        $expected[1] = [
            'token'               => 23,
            'name'                => '$promotedToo',
            'content'             => 'ReadOnly private string|bool &$promotedToo',
            'has_attributes'      => false,
            'pass_by_reference'   => true,
            'reference_token'     => 22,
            'variable_length'     => false,
            'variadic_token'      => false,
            'type_hint'           => 'string|bool',
            'type_hint_token'     => 18,
            'type_hint_end_token' => 20,
            'nullable_type'       => false,
            'property_visibility' => 'private',
            'visibility_token'    => 16,
            'property_readonly'   => true,
            'readonly_token'      => 14,
            'comma_token'         => false,
        ];

        $this->getMethodParametersTestHelper('/* ' . __FUNCTION__ . ' */', $expected);
    }

    /**
     * Verify recognition of PHP8 constructor with property promotion using PHP 8.1 readonly keyword
     * without a property type.
     *
     * @return void
     */
    public function testPHP81ConstructorPropertyPromotionWithReadOnlyNoTypeDeclaration()
    {
        // Offsets are relative to the T_FUNCTION token.
        $expected    = [];
        $expected[0] = [
            'token'               => 8,
            'name'                => '$promotedProp',
            'content'             => 'public readonly $promotedProp',
            'has_attributes'      => false,
            'pass_by_reference'   => false,
            'reference_token'     => false,
            'variable_length'     => false,
            'variadic_token'      => false,
            'type_hint'           => '',
            'type_hint_token'     => false,
            'type_hint_end_token' => false,
            'nullable_type'       => false,
            'property_visibility' => 'public',
            'visibility_token'    => 4,
            'property_readonly'   => true,
            'readonly_token'      => 6,
            'comma_token'         => 9,
        ];
        $expected[1] = [
            'token'               => 16,
            'name'                => '$promotedToo',
            'content'             => 'ReadOnly private &$promotedToo',
            'has_attributes'      => false,
            'pass_by_reference'   => true,
            'reference_token'     => 15,
            'variable_length'     => false,
            'variadic_token'      => false,
            'type_hint'           => '',
            'type_hint_token'     => false,
            'type_hint_end_token' => false,
            'nullable_type'       => false,
            'property_visibility' => 'private',
            'visibility_token'    => 13,
            'property_readonly'   => true,
            'readonly_token'      => 11,
            'comma_token'         => false,
        ];

        $this->getMethodParametersTestHelper('/* ' . __FUNCTION__ . ' */', $expected);
    }

    /**
     * Verify recognition of PHP8 constructor with property promotion using PHP 8.1 readonly
     * keyword without explicit visibility.
     *
     * @return void
     */
    public function testPHP81ConstructorPropertyPromotionWithOnlyReadOnly()
    {
        // Offsets are relative to the T_FUNCTION token.
        $expected    = [];
        $expected[0] = [
            'token'               => 10,
            'name'                => '$promotedProp',
            'content'             => 'readonly Foo&Bar $promotedProp',
            'has_attributes'      => false,
            'pass_by_reference'   => false,
            'reference_token'     => false,
            'variable_length'     => false,
            'variadic_token'      => false,
            'type_hint'           => 'Foo&Bar',
            'type_hint_token'     => 6,
            'type_hint_end_token' => 8,
            'nullable_type'       => false,
            'property_visibility' => 'public',
            'visibility_token'    => false,
            'property_readonly'   => true,
            'readonly_token'      => 4,
            'comma_token'         => 11,
        ];
        $expected[1] = [
            'token'               => 18,
            'name'                => '$promotedToo',
            'content'             => 'readonly ?bool $promotedToo',
            'has_attributes'      => false,
            'pass_by_reference'   => false,
            'reference_token'     => false,
            'variable_length'     => false,
            'variadic_token'      => false,
            'type_hint'           => '?bool',
            'type_hint_token'     => 16,
            'type_hint_end_token' => 16,
            'nullable_type'       => true,
            'property_visibility' => 'public',
            'visibility_token'    => false,
            'property_readonly'   => true,
            'readonly_token'      => 13,
            'comma_token'         => 19,
        ];

        $this->getMethodParametersTestHelper('/* ' . __FUNCTION__ . ' */', $expected);
    }

    /**
     * Verify behaviour when a non-constructor function uses PHP 8 property promotion syntax.
     *
     * @return void
     */
    public function testPHP8ConstructorPropertyPromotionGlobalFunction()
    {
        // Offsets are relative to the T_FUNCTION token.
        $expected    = [];
        $expected[0] = [
            'token'               => 6,
            'name'                => '$x',
            'content'             => 'private $x',
            'has_attributes'      => false,
            'pass_by_reference'   => false,
            'reference_token'     => false,
            'variable_length'     => false,
            'variadic_token'      => false,
            'type_hint'           => '',
            'type_hint_token'     => false,
            'type_hint_end_token' => false,
            'nullable_type'       => false,
            'property_visibility' => 'private',
            'visibility_token'    => 4,
            'property_readonly'   => false,
            'comma_token'         => false,
        ];

        $this->getMethodParametersTestHelper('/* ' . __FUNCTION__ . ' */', $expected);
    }

    /**
     * Verify behaviour when an abstract constructor uses PHP 8 property promotion syntax.
     *
     * @return void
     */
    public function testPHP8ConstructorPropertyPromotionAbstractMethod()
    {
        // Offsets are relative to the T_FUNCTION token.
        $expected    = [];
        $expected[0] = [
            'token'               => 8,
            'name'                => '$y',
            'content'             => 'public callable $y',
            'has_attributes'      => false,
            'pass_by_reference'   => false,
            'reference_token'     => false,
            'variable_length'     => false,
            'variadic_token'      => false,
            'type_hint'           => 'callable',
            'type_hint_token'     => 6,
            'type_hint_end_token' => 6,
            'nullable_type'       => false,
            'property_visibility' => 'public',
            'visibility_token'    => 4,
            'property_readonly'   => false,
            'comma_token'         => 9,
        ];
        $expected[1] = [
            'token'               => 14,
            'name'                => '$x',
            'content'             => 'private ...$x',
            'has_attributes'      => false,
            'pass_by_reference'   => false,
            'reference_token'     => false,
            'variable_length'     => true,
            'variadic_token'      => 13,
            'type_hint'           => '',
            'type_hint_token'     => false,
            'type_hint_end_token' => false,
            'nullable_type'       => false,
            'property_visibility' => 'private',
            'visibility_token'    => 11,
            'property_readonly'   => false,
            'comma_token'         => false,
        ];

        $this->getMethodParametersTestHelper('/* ' . __FUNCTION__ . ' */', $expected);
    }

    /**
     * Verify and document behaviour when there are comments within a parameter declaration.
     *
     * @return void
     */
    public function testCommentsInParameter()
    {
        // Offsets are relative to the T_FUNCTION token.
        $expected    = [];
        $expected[0] = [
            'token'               => 19,
            'name'                => '$param',
            'content'             => '// Leading comment.
    ?MyClass /*-*/ & /*-*/.../*-*/ $param /*-*/ = /*-*/ \'default value\' . /*-*/ \'second part\' // Trailing comment.',
            'default'             => '\'default value\' . /*-*/ \'second part\' // Trailing comment.',
            'default_token'       => 27,
            'default_equal_token' => 23,
            'has_attributes'      => false,
            'pass_by_reference'   => true,
            'reference_token'     => 13,
            'variable_length'     => true,
            'variadic_token'      => 16,
            'type_hint'           => '?MyClass',
            'type_hint_token'     => 9,
            'type_hint_end_token' => 9,
            'nullable_type'       => true,
            'comma_token'         => false,
        ];

        $this->getMethodParametersTestHelper('/* ' . __FUNCTION__ . ' */', $expected);
    }

    /**
     * Verify behaviour when parameters have attributes attached.
     *
     * @return void
     */
    public function testParameterAttributesInFunctionDeclaration()
    {
        $php8Names = parent::usesPhp8NameTokens();

        // Offsets are relative to the T_FUNCTION token.
        $expected    = [];
        $expected[0] = [
            'token'               => ($php8Names === true) ? 14 : 17,
            'name'                => '$constructorPropPromTypedParamSingleAttribute',
            'content'             => '#[\MyExample\MyAttribute] private string'
                . ' $constructorPropPromTypedParamSingleAttribute',
            'has_attributes'      => true,
            'pass_by_reference'   => false,
            'reference_token'     => false,
            'variable_length'     => false,
            'variadic_token'      => false,
            'type_hint'           => 'string',
            'type_hint_token'     => ($php8Names === true) ? 12 : 15,
            'type_hint_end_token' => ($php8Names === true) ? 12 : 15,
            'nullable_type'       => false,
            'property_visibility' => 'private',
            'visibility_token'    => ($php8Names === true) ? 10 : 13,
            'property_readonly'   => false,
            'comma_token'         => ($php8Names === true) ? 15 : 18,
        ];
        $expected[1] = [
            'token'               => ($php8Names === true) ? 36 : 39,
            'name'                => '$typedParamSingleAttribute',
            'content'             => '#[MyAttr([1, 2])]
        Type|false
        $typedParamSingleAttribute',
            'has_attributes'      => true,
            'pass_by_reference'   => false,
            'reference_token'     => false,
            'variable_length'     => false,
            'variadic_token'      => false,
            'type_hint'           => 'Type|false',
            'type_hint_token'     => ($php8Names === true) ? 31 : 34,
            'type_hint_end_token' => ($php8Names === true) ? 33 : 36,
            'nullable_type'       => false,
            'comma_token'         => ($php8Names === true) ? 37 : 40,
        ];
        $expected[2] = [
            'token'               => ($php8Names === true) ? 56 : 59,
            'name'                => '$nullableTypedParamMultiAttribute',
            'content'             => '#[MyAttribute(1234), MyAttribute(5678)] ?int $nullableTypedParamMultiAttribute',
            'has_attributes'      => true,
            'pass_by_reference'   => false,
            'reference_token'     => false,
            'variable_length'     => false,
            'variadic_token'      => false,
            'type_hint'           => '?int',
            'type_hint_token'     => ($php8Names === true) ? 54 : 57,
            'type_hint_end_token' => ($php8Names === true) ? 54 : 57,
            'nullable_type'       => true,
            'comma_token'         => ($php8Names === true) ? 57 : 60,
        ];
        $expected[3] = [
            'token'               => ($php8Names === true) ? 71 : 74,
            'name'                => '$nonTypedParamTwoAttributes',
            'content'             => '#[WithoutArgument] #[SingleArgument(0)] $nonTypedParamTwoAttributes',
            'has_attributes'      => true,
            'pass_by_reference'   => false,
            'reference_token'     => false,
            'variable_length'     => false,
            'variadic_token'      => false,
            'type_hint'           => '',
            'type_hint_token'     => false,
            'type_hint_end_token' => false,
            'nullable_type'       => false,
            'comma_token'         => ($php8Names === true) ? 72 : 75,
        ];
        $expected[4] = [
            'token'               => ($php8Names === true) ? 92 : 95,
            'name'                => '$otherParam',
            'content'             => '#[MyAttribute(array("key" => "value"))]
        &...$otherParam',
            'has_attributes'      => true,
            'pass_by_reference'   => true,
            'reference_token'     => ($php8Names === true) ? 90 : 93,
            'variable_length'     => true,
            'variadic_token'      => ($php8Names === true) ? 91 : 94,
            'type_hint'           => '',
            'type_hint_token'     => false,
            'type_hint_end_token' => false,
            'nullable_type'       => false,
            'comma_token'         => ($php8Names === true) ? 93 : 96,
        ];

        $this->getMethodParametersTestHelper('/* ' . __FUNCTION__ . ' */', $expected);
    }

    /**
     * Verify recognition of PHP8.1 intersection type declaration.
     *
     * @return void
     */
    public function testPHP8IntersectionTypes()
    {
        // Offsets are relative to the T_FUNCTION token.
        $expected    = [];
        $expected[0] = [
            'token'               => 8,
            'name'                => '$obj1',
            'content'             => 'Foo&Bar $obj1',
            'has_attributes'      => false,
            'pass_by_reference'   => false,
            'reference_token'     => false,
            'variable_length'     => false,
            'variadic_token'      => false,
            'type_hint'           => 'Foo&Bar',
            'type_hint_token'     => 4,
            'type_hint_end_token' => 6,
            'nullable_type'       => false,
            'comma_token'         => 9,
        ];
        $expected[1] = [
            'token'               => 15,
            'name'                => '$obj2',
            'content'             => 'Boo&Bar $obj2',
            'has_attributes'      => false,
            'pass_by_reference'   => false,
            'reference_token'     => false,
            'variable_length'     => false,
            'variadic_token'      => false,
            'type_hint'           => 'Boo&Bar',
            'type_hint_token'     => 11,
            'type_hint_end_token' => 13,
            'nullable_type'       => false,
            'comma_token'         => false,
        ];

        $this->getMethodParametersTestHelper('/* ' . __FUNCTION__ . ' */', $expected);
    }

    /**
     * Verify recognition of PHP8 intersection type declaration when the variable
     * has either a spread operator or a reference.
     *
     * @return void
     */
    public function testPHP81IntersectionTypesWithSpreadOperatorAndReference()
    {
        // Offsets are relative to the T_FUNCTION token.
        $expected    = [];
        $expected[0] = [
            'token'               => 9,
            'name'                => '$paramA',
            'content'             => 'Boo&Bar &$paramA',
            'has_attributes'      => false,
            'pass_by_reference'   => true,
            'reference_token'     => 8,
            'variable_length'     => false,
            'variadic_token'      => false,
            'type_hint'           => 'Boo&Bar',
            'type_hint_token'     => 4,
            'type_hint_end_token' => 6,
            'nullable_type'       => false,
            'comma_token'         => 10,
        ];
        $expected[1] = [
            'token'               => 17,
            'name'                => '$paramB',
            'content'             => 'Foo&Bar ...$paramB',
            'has_attributes'      => false,
            'pass_by_reference'   => false,
            'reference_token'     => false,
            'variable_length'     => true,
            'variadic_token'      => 16,
            'type_hint'           => 'Foo&Bar',
            'type_hint_token'     => 12,
            'type_hint_end_token' => 14,
            'nullable_type'       => false,
            'comma_token'         => false,
        ];

        $this->getMethodParametersTestHelper('/* ' . __FUNCTION__ . ' */', $expected);
    }

    /**
     * Verify recognition of PHP8.1 intersection type declaration with more types.
     *
     * @return void
     */
    public function testPHP81MoreIntersectionTypes()
    {
        $php8Names = parent::usesPhp8NameTokens();

        // Offsets are relative to the T_FUNCTION token.
        $expected    = [];
        $expected[0] = [
            'token'               => ($php8Names === true) ? 10 : 16,
            'name'                => '$var',
            'content'             => 'MyClassA&\Package\MyClassB&\Package\MyClassC $var',
            'has_attributes'      => false,
            'pass_by_reference'   => false,
            'reference_token'     => false,
            'variable_length'     => false,
            'variadic_token'      => false,
            'type_hint'           => 'MyClassA&\Package\MyClassB&\Package\MyClassC',
            'type_hint_token'     => 4,
            'type_hint_end_token' => ($php8Names === true) ? 8 : 14,
            'nullable_type'       => false,
            'comma_token'         => false,
        ];

        $this->getMethodParametersTestHelper('/* ' . __FUNCTION__ . ' */', $expected);
    }

    /**
     * Verify recognition of PHP8.1 intersection type declaration with illegal simple types.
     *
     * @return void
     */
    public function testPHP81IllegalIntersectionTypes()
    {
        // Offsets are relative to the T_FUNCTION token.
        $expected    = [];
        $expected[0] = [
            'token'               => 7,
            'name'                => '$numeric_string',
            'content'             => 'string&int $numeric_string',
            'has_attributes'      => false,
            'pass_by_reference'   => false,
            'reference_token'     => false,
            'variable_length'     => false,
            'variadic_token'      => false,
            'type_hint'           => 'string&int',
            'type_hint_token'     => 3,
            'type_hint_end_token' => 5,
            'nullable_type'       => false,
            'comma_token'         => false,
        ];

        $this->getMethodParametersTestHelper('/* ' . __FUNCTION__ . ' */', $expected);
    }

    /**
     * Verify recognition of PHP8.1 intersection type declaration with (illegal) nullability.
     *
     * @return void
     */
    public function testPHP81NullableIntersectionTypes()
    {
        // Offsets are relative to the T_FUNCTION token.
        $expected    = [];
        $expected[0] = [
            'token'               => 8,
            'name'                => '$object',
            'content'             => '?Foo&Bar $object',
            'has_attributes'      => false,
            'pass_by_reference'   => false,
            'reference_token'     => false,
            'variable_length'     => false,
            'variadic_token'      => false,
            'type_hint'           => '?Foo&Bar',
            'type_hint_token'     => 4,
            'type_hint_end_token' => 6,
            'nullable_type'       => true,
            'comma_token'         => false,
        ];

        $this->getMethodParametersTestHelper('/* ' . __FUNCTION__ . ' */', $expected);
    }

    /**
     * Verify recognition of PHP 8.2 stand-alone `true` type.
     *
     * @return void
     */
    public function testPHP82PseudoTypeTrue()
    {
        // Offsets are relative to the T_FUNCTION token.
        $expected    = [];
        $expected[0] = [
            'token'               => 7,
            'name'                => '$var',
            'content'             => '?true $var = true',
            'default'             => 'true',
            'default_token'       => 11,
            'default_equal_token' => 9,
            'has_attributes'      => false,
            'pass_by_reference'   => false,
            'reference_token'     => false,
            'variable_length'     => false,
            'variadic_token'      => false,
            'type_hint'           => '?true',
            'type_hint_token'     => 5,
            'type_hint_end_token' => 5,
            'nullable_type'       => true,
            'comma_token'         => false,
        ];

        $this->getMethodParametersTestHelper('/* ' . __FUNCTION__ . ' */', $expected);
    }

    /**
     * Verify recognition of PHP 8.2 type declaration with (illegal) type false combined with type true.
     *
     * @return void
     */
    public function testPHP82PseudoTypeFalseAndTrue()
    {
        // Offsets are relative to the T_FUNCTION token.
        $expected    = [];
        $expected[0] = [
            'token'               => 8,
            'name'                => '$var',
            'content'             => 'true|false $var = true',
            'default'             => 'true',
            'default_token'       => 12,
            'default_equal_token' => 10,
            'has_attributes'      => false,
            'pass_by_reference'   => false,
            'reference_token'     => false,
            'variable_length'     => false,
            'variadic_token'      => false,
            'type_hint'           => 'true|false',
            'type_hint_token'     => 4,
            'type_hint_end_token' => 6,
            'nullable_type'       => false,
            'comma_token'         => false,
        ];

        $this->getMethodParametersTestHelper('/* ' . __FUNCTION__ . ' */', $expected);
    }

    /**
     * Verify behaviour when the default value uses the "new" keyword, as is allowed per PHP 8.1.
     *
     * @return void
     */
    public function testPHP81NewInInitializers()
    {
        $php8Names = parent::usesPhp8NameTokens();

        // Offsets are relative to the T_FUNCTION token.
        $expected    = [];
        $expected[0] = [
            'token'               => 8,
            'name'                => '$new',
            'content'             => 'TypeA $new = new TypeA(self::CONST_VALUE)',
            'default'             => 'new TypeA(self::CONST_VALUE)',
            'default_token'       => 12,
            'default_equal_token' => 10,
            'has_attributes'      => false,
            'pass_by_reference'   => false,
            'reference_token'     => false,
            'variable_length'     => false,
            'variadic_token'      => false,
            'type_hint'           => 'TypeA',
            'type_hint_token'     => 6,
            'type_hint_end_token' => 6,
            'nullable_type'       => false,
            'comma_token'         => 20,
        ];
        $expected[1] = [
            'token'               => ($php8Names === true) ? 25 : 28,
            'name'                => '$newToo',
            'content'             => '\Package\TypeB $newToo = new \Package\TypeB(10, \'string\')',
            'default'             => "new \Package\TypeB(10, 'string')",
            'default_token'       => ($php8Names === true) ? 29 : 32,
            'default_equal_token' => ($php8Names === true) ? 27 : 30,
            'has_attributes'      => false,
            'pass_by_reference'   => false,
            'reference_token'     => false,
            'variable_length'     => false,
            'variadic_token'      => false,
            'type_hint'           => '\Package\TypeB',
            'type_hint_token'     => 23,
            'type_hint_end_token' => ($php8Names === true) ? 23 : 26,
            'nullable_type'       => false,
            'comma_token'         => ($php8Names === true) ? 38 : 44,
        ];

        $this->getMethodParametersTestHelper('/* ' . __FUNCTION__ . ' */', $expected);
    }

    /**
     * Verify recognition of 8.2 DNF parameter type declarations.
     *
     * @return void
     */
    public function testPHP82DNFTypes()
    {
        $php8Names = parent::usesPhp8NameTokens();

        // Offsets are relative to the T_FUNCTION token.
        $expected    = [];
        $expected[0] = [
            'token'               => 21,
            'name'                => '$obj1',
            'content'             => '#[MyAttribute]
    false|(Foo&Bar)|true $obj1',
            'has_attributes'      => true,
            'pass_by_reference'   => false,
            'reference_token'     => false,
            'variable_length'     => false,
            'variadic_token'      => false,
            'type_hint'           => 'false|(Foo&Bar)|true',
            'type_hint_token'     => 11,
            'type_hint_end_token' => 19,
            'nullable_type'       => false,
            'comma_token'         => 22,
        ];
        $expected[1] = [
            'token'               => ($php8Names === true) ? 37 : 41,
            'name'                => '$obj2',
            'content'             => '(\Boo&\Pck\Bar)|(Boo&Baz) $obj2 = new Boo()',
            'default'             => 'new Boo()',
            'default_token'       => ($php8Names === true) ? 41 : 45,
            'default_equal_token' => ($php8Names === true) ? 39 : 43,
            'has_attributes'      => false,
            'pass_by_reference'   => false,
            'reference_token'     => false,
            'variable_length'     => false,
            'variadic_token'      => false,
            'type_hint'           => '(\Boo&\Pck\Bar)|(Boo&Baz)',
            'type_hint_token'     => 25,
            'type_hint_end_token' => ($php8Names === true) ? 35 : 39,
            'nullable_type'       => false,
            'comma_token'         => false,
        ];

        $this->getMethodParametersTestHelper('/* ' . __FUNCTION__ . ' */', $expected);
    }

    /**
     * Verify recognition of PHP 8.2 DNF parameter type declarations when the variable
     * has either a spread operator or a reference.
     *
     * @return void
     */
    public function testPHP82DNFTypesWithSpreadOperatorAndReference()
    {
        // Offsets are relative to the T_FUNCTION token.
        $expected    = [];
        $expected[0] = [
            'token'               => 13,
            'name'                => '$paramA',
            'content'             => '(Countable&MeMe)|iterable &$paramA',
            'has_attributes'      => false,
            'pass_by_reference'   => true,
            'reference_token'     => 12,
            'variable_length'     => false,
            'variadic_token'      => false,
            'type_hint'           => '(Countable&MeMe)|iterable',
            'type_hint_token'     => 4,
            'type_hint_end_token' => 10,
            'nullable_type'       => false,
            'comma_token'         => 14,
        ];
        $expected[1] = [
            'token'               => 25,
            'name'                => '$paramB',
            'content'             => 'true|(Foo&Bar) ...$paramB',
            'has_attributes'      => false,
            'pass_by_reference'   => false,
            'reference_token'     => false,
            'variable_length'     => true,
            'variadic_token'      => 24,
            'type_hint'           => 'true|(Foo&Bar)',
            'type_hint_token'     => 16,
            'type_hint_end_token' => 22,
            'nullable_type'       => false,
            'comma_token'         => false,
        ];

        $this->getMethodParametersTestHelper('/* ' . __FUNCTION__ . ' */', $expected);
    }

    /**
     * Verify recognition of PHP 8.2 DNF parameter type declarations using the nullability operator (not allowed).
     *
     * @return void
     */
    public function testPHP82DNFTypesIllegalNullable()
    {
        $php8Names = parent::usesPhp8NameTokens();

        // Offsets are relative to the T_FUNCTION token.
        $expected    = [];
        $expected[0] = [
            'token'               => ($php8Names === true) ? 21 : 27,
            'name'                => '$var',
            'content'             => '? ( MyClassA & /*comment*/ \Package\MyClassB & \Package\MyClassC ) $var',
            'has_attributes'      => false,
            'pass_by_reference'   => false,
            'reference_token'     => false,
            'variable_length'     => false,
            'variadic_token'      => false,
            'type_hint'           => '?(MyClassA&\Package\MyClassB&\Package\MyClassC)',
            'type_hint_token'     => 5,
            'type_hint_end_token' => ($php8Names === true) ? 19 : 25,
            'nullable_type'       => true,
            'comma_token'         => false,
        ];

        $this->getMethodParametersTestHelper('/* ' . __FUNCTION__ . ' */', $expected);
    }

    /**
     * Verify recognition of PHP 8.2 DNF parameter type declarations in an arrow function.
     *
     * @return void
     */
    public function testPHP82DNFTypesInArrow()
    {
        // Offsets are relative to the T_FUNCTION token.
        $expected    = [];
        $expected[0] = [
            'token'               => 12,
            'name'                => '$range',
            'content'             => '(Hi&Ho)|FALSE &...$range',
            'has_attributes'      => false,
            'pass_by_reference'   => true,
            'reference_token'     => 10,
            'variable_length'     => true,
            'variadic_token'      => 11,
            'type_hint'           => '(Hi&Ho)|FALSE',
            'type_hint_token'     => 2,
            'type_hint_end_token' => 8,
            'nullable_type'       => false,
            'comma_token'         => false,
        ];

        $this->getMethodParametersTestHelper('/* ' . __FUNCTION__ . ' */', $expected);
    }

    /**
     * Verify handling of a closure.
     *
     * @return void
     */
    public function testClosure()
    {
        // Offsets are relative to the T_FUNCTION token.
        $expected    = [];
        $expected[0] = [
            'token'               => 3,
            'name'                => '$a',
            'content'             => '$a = \'test\'',
            'default'             => "'test'",
            'default_token'       => 7,
            'default_equal_token' => 5,
            'has_attributes'      => false,
            'pass_by_reference'   => false,
            'reference_token'     => false,
            'variable_length'     => false,
            'variadic_token'      => false,
            'type_hint'           => '',
            'type_hint_token'     => false,
            'type_hint_end_token' => false,
            'nullable_type'       => false,
            'comma_token'         => false,
        ];

        $this->getMethodParametersTestHelper('/* ' . __FUNCTION__ . ' */', $expected);
    }

    /**
     * Verify handling of a closure T_USE token correctly.
     *
     * @return void
     */
    public function testClosureUse()
    {
        // Offsets are relative to the T_USE token.
        $expected    = [];
        $expected[0] = [
            'token'               => 3,
            'name'                => '$foo',
            'content'             => '$foo',
            'has_attributes'      => false,
            'pass_by_reference'   => false,
            'reference_token'     => false,
            'variable_length'     => false,
            'variadic_token'      => false,
            'type_hint'           => '',
            'type_hint_token'     => false,
            'type_hint_end_token' => false,
            'nullable_type'       => false,
            'comma_token'         => 4,
        ];
        $expected[1] = [
            'token'               => 6,
            'name'                => '$bar',
            'content'             => '$bar',
            'has_attributes'      => false,
            'pass_by_reference'   => false,
            'reference_token'     => false,
            'variable_length'     => false,
            'variadic_token'      => false,
            'type_hint'           => '',
            'type_hint_token'     => false,
            'type_hint_end_token' => false,
            'nullable_type'       => false,
            'comma_token'         => false,
        ];

        $this->getMethodParametersTestHelper('/* ' . __FUNCTION__ . ' */', $expected, [T_USE]);
    }

    /**
     * Verify function declarations with trailing commas are handled correctly.
     *
     * @return void
     */
    public function testFunctionParamListWithTrailingComma()
    {
        // Offsets are relative to the T_FUNCTION token.
        $expected    = [];
        $expected[0] = [
            'token'               => 9,
            'name'                => '$foo',
            'content'             => '?string $foo  /*comment*/',
            'has_attributes'      => false,
            'pass_by_reference'   => false,
            'reference_token'     => false,
            'variable_length'     => false,
            'variadic_token'      => false,
            'type_hint'           => '?string',
            'type_hint_token'     => 7,
            'type_hint_end_token' => 7,
            'nullable_type'       => true,
            'comma_token'         => 13,
        ];
        $expected[1] = [
            'token'               => 16,
            'name'                => '$bar',
            'content'             => '$bar = 0',
            'default'             => '0',
            'default_token'       => 20,
            'default_equal_token' => 18,
            'has_attributes'      => false,
            'pass_by_reference'   => false,
            'reference_token'     => false,
            'variable_length'     => false,
            'variadic_token'      => false,
            'type_hint'           => '',
            'type_hint_token'     => false,
            'type_hint_end_token' => false,
            'nullable_type'       => false,
            'comma_token'         => 21,
        ];

        $this->getMethodParametersTestHelper('/* ' . __FUNCTION__ . ' */', $expected);
    }

    /**
     * Verify closure declarations with trailing commas are handled correctly.
     *
     * @return void
     */
    public function testClosureParamListWithTrailingComma()
    {
        // Offsets are relative to the T_FUNCTION token.
        $expected    = [];
        $expected[0] = [
            'token'               => 4,
            'name'                => '$foo',
            'content'             => '$foo',
            'has_attributes'      => false,
            'pass_by_reference'   => false,
            'reference_token'     => false,
            'variable_length'     => false,
            'variadic_token'      => false,
            'type_hint'           => '',
            'type_hint_token'     => false,
            'type_hint_end_token' => false,
            'nullable_type'       => false,
            'comma_token'         => 5,
        ];
        $expected[1] = [
            'token'               => 8,
            'name'                => '$bar',
            'content'             => '$bar',
            'has_attributes'      => false,
            'pass_by_reference'   => false,
            'reference_token'     => false,
            'variable_length'     => false,
            'variadic_token'      => false,
            'type_hint'           => '',
            'type_hint_token'     => false,
            'type_hint_end_token' => false,
            'nullable_type'       => false,
            'comma_token'         => 9,
        ];

        $this->getMethodParametersTestHelper('/* ' . __FUNCTION__ . ' */', $expected);
    }

    /**
     * Verify arrow function declarations with trailing commas are handled correctly.
     *
     * @return void
     */
    public function testArrowFunctionParamListWithTrailingComma()
    {
        // Offsets are relative to the T_FN token.
        $expected    = [];
        $expected[0] = [
            'token'               => 6,
            'name'                => '$a',
            'content'             => '?int $a',
            'has_attributes'      => false,
            'pass_by_reference'   => false,
            'reference_token'     => false,
            'variable_length'     => false,
            'variadic_token'      => false,
            'type_hint'           => '?int',
            'type_hint_token'     => 4,
            'type_hint_end_token' => 4,
            'nullable_type'       => true,
            'comma_token'         => 8,
        ];
        $expected[1] = [
            'token'               => 11,
            'name'                => '$b',
            'content'             => '...$b',
            'has_attributes'      => false,
            'pass_by_reference'   => false,
            'reference_token'     => false,
            'variable_length'     => true,
            'variadic_token'      => 10,
            'type_hint'           => '',
            'type_hint_token'     => false,
            'type_hint_end_token' => false,
            'nullable_type'       => false,
            'comma_token'         => 12,
        ];

        $this->getMethodParametersTestHelper('/* ' . __FUNCTION__ . ' */', $expected);
    }

    /**
     * Verify closure T_USE statements with trailing commas are handled correctly.
     *
     * @return void
     */
    public function testClosureUseWithTrailingComma()
    {
        // Offsets are relative to the T_USE token.
        $expected    = [];
        $expected[0] = [
            'token'               => 4,
            'name'                => '$foo',
            'content'             => '$foo  /*comment*/',
            'has_attributes'      => false,
            'pass_by_reference'   => false,
            'reference_token'     => false,
            'variable_length'     => false,
            'variadic_token'      => false,
            'type_hint'           => '',
            'type_hint_token'     => false,
            'type_hint_end_token' => false,
            'nullable_type'       => false,
            'comma_token'         => 8,
        ];
        $expected[1] = [
            'token'               => 11,
            'name'                => '$bar',
            'content'             => '$bar',
            'has_attributes'      => false,
            'pass_by_reference'   => false,
            'reference_token'     => false,
            'variable_length'     => false,
            'variadic_token'      => false,
            'type_hint'           => '',
            'type_hint_token'     => false,
            'type_hint_end_token' => false,
            'nullable_type'       => false,
            'comma_token'         => 12,
        ];

        $this->getMethodParametersTestHelper('/* ' . __FUNCTION__ . ' */', $expected, [T_USE]);
    }

    /**
     * Test helper.
     *
     * @param string                                     $marker     The comment which preceeds the test.
     * @param array<int, array<string, int|string|bool>> $expected   The expected function output.
     * @param int|string|array<int|string>               $targetType Optional. The token type to search for after $marker.
     *                                                               Defaults to the function/closure/arrow tokens.
     *
     * @return void
     */
    protected function getMethodParametersTestHelper($marker, $expected, $targetType = [T_FUNCTION, T_CLOSURE, T_FN])
    {
        $target = $this->getTargetToken($marker, $targetType);
        $found  = BCFile::getMethodParameters(self::$phpcsFile, $target);

        // Convert offsets to absolute positions in the token stream.
        foreach ($expected as $key => $param) {
            $expected[$key]['token'] += $target;

            if (\is_int($param['reference_token']) === true) {
                $expected[$key]['reference_token'] += $target;
            }
            if (\is_int($param['variadic_token']) === true) {
                $expected[$key]['variadic_token'] += $target;
            }
            if (\is_int($param['type_hint_token']) === true) {
                $expected[$key]['type_hint_token'] += $target;
            }
            if (\is_int($param['type_hint_end_token']) === true) {
                $expected[$key]['type_hint_end_token'] += $target;
            }
            if (\is_int($param['comma_token']) === true) {
                $expected[$key]['comma_token'] += $target;
            }
            if (isset($param['default_token'])) {
                $expected[$key]['default_token'] += $target;
            }
            if (isset($param['default_equal_token'])) {
                $expected[$key]['default_equal_token'] += $target;
            }
            if (isset($param['visibility_token']) && \is_int($param['visibility_token']) === true) {
                $expected[$key]['visibility_token'] += $target;
            }
            if (isset($param['readonly_token'])) {
                $expected[$key]['readonly_token'] += $target;
            }
        }

        $this->assertSame($expected, $found);
    }
}
