<?php
/**
 * Tests for the \PHP_CodeSniffer\Files\File::isReference method.
 *
 * @author    Greg Sherwood <gsherwood@squiz.net>
 * @copyright 2006-2015 Squiz Pty Ltd (ABN 77 084 670 600)
 * @license   https://github.com/PHPCSStandards/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
 */

namespace PHP_CodeSniffer\Tests\Core\File;

use PHP_CodeSniffer\Tests\Core\AbstractMethodUnitTest;

/**
 * Tests for the \PHP_CodeSniffer\Files\File::isReference method.
 *
 * @covers \PHP_CodeSniffer\Files\File::isReference
 */
final class IsReferenceTest extends AbstractMethodUnitTest
{


    /**
     * Test that false is returned when a non-"bitwise and" token is passed.
     *
     * @param string            $testMarker   Comment which precedes the test case.
     * @param array<int|string> $targetTokens Type of tokens to look for.
     *
     * @dataProvider dataNotBitwiseAndToken
     *
     * @return void
     */
    public function testNotBitwiseAndToken($testMarker, $targetTokens)
    {
        $targetTokens[] = T_BITWISE_AND;

        $target = $this->getTargetToken($testMarker, $targetTokens);
        $this->assertFalse(self::$phpcsFile->isReference($target));

    }//end testNotBitwiseAndToken()


    /**
     * Data provider.
     *
     * @see testNotBitwiseAndToken()
     *
     * @return array<string, array<string, string|array<int|string>>>
     */
    public static function dataNotBitwiseAndToken()
    {
        return [
            'Not ampersand token at all'     => [
                'testMarker'   => '/* testBitwiseAndA */',
                'targetTokens' => [T_STRING],
            ],
            'ampersand in intersection type' => [
                'testMarker'   => '/* testIntersectionIsNotReference */',
                'targetTokens' => [T_TYPE_INTERSECTION],
            ],
            'ampersand in DNF type'          => [
                'testMarker'   => '/* testDNFTypeIsNotReference */',
                'targetTokens' => [T_TYPE_INTERSECTION],
            ],
        ];

    }//end dataNotBitwiseAndToken()


    /**
     * Test correctly identifying whether a "bitwise and" token is a reference or not.
     *
     * @param string $testMarker Comment which precedes the test case.
     * @param bool   $expected   Expected function output.
     *
     * @dataProvider dataIsReference
     *
     * @return void
     */
    public function testIsReference($testMarker, $expected)
    {
        $bitwiseAnd = $this->getTargetToken($testMarker, T_BITWISE_AND);
        $result     = self::$phpcsFile->isReference($bitwiseAnd);
        $this->assertSame($expected, $result);

    }//end testIsReference()


    /**
     * Data provider for the IsReference test.
     *
     * @see testIsReference()
     *
     * @return array<string, array<string, string|bool>>
     */
    public static function dataIsReference()
    {
        return [
            'issue-1971-list-first-in-file'                                                                     => [
                'testMarker' => '/* testTokenizerIssue1971PHPCSlt330gt271A */',
                'expected'   => true,
            ],
            'issue-1971-list-first-in-file-nested'                                                              => [
                'testMarker' => '/* testTokenizerIssue1971PHPCSlt330gt271B */',
                'expected'   => true,
            ],
            'bitwise and: param in function call'                                                               => [
                'testMarker' => '/* testBitwiseAndA */',
                'expected'   => false,
            ],
            'bitwise and: in unkeyed short array, first value'                                                  => [
                'testMarker' => '/* testBitwiseAndB */',
                'expected'   => false,
            ],
            'bitwise and: in unkeyed short array, last value'                                                   => [
                'testMarker' => '/* testBitwiseAndC */',
                'expected'   => false,
            ],
            'bitwise and: in unkeyed long array, last value'                                                    => [
                'testMarker' => '/* testBitwiseAndD */',
                'expected'   => false,
            ],
            'bitwise and: in keyed short array, last value'                                                     => [
                'testMarker' => '/* testBitwiseAndE */',
                'expected'   => false,
            ],
            'bitwise and: in keyed long array, last value'                                                      => [
                'testMarker' => '/* testBitwiseAndF */',
                'expected'   => false,
            ],
            'bitwise and: in assignment'                                                                        => [
                'testMarker' => '/* testBitwiseAndG */',
                'expected'   => false,
            ],
            'bitwise and: in param default value in function declaration'                                       => [
                'testMarker' => '/* testBitwiseAndH */',
                'expected'   => false,
            ],
            'bitwise and: in param default value in closure declaration'                                        => [
                'testMarker' => '/* testBitwiseAndI */',
                'expected'   => false,
            ],
            'reference: function declared to return by reference'                                               => [
                'testMarker' => '/* testFunctionReturnByReference */',
                'expected'   => true,
            ],
            'reference: only param in function declaration, pass by reference'                                  => [
                'testMarker' => '/* testFunctionPassByReferenceA */',
                'expected'   => true,
            ],
            'reference: last param in function declaration, pass by reference'                                  => [
                'testMarker' => '/* testFunctionPassByReferenceB */',
                'expected'   => true,
            ],
            'reference: only param in closure declaration, pass by reference'                                   => [
                'testMarker' => '/* testFunctionPassByReferenceC */',
                'expected'   => true,
            ],
            'reference: last param in closure declaration, pass by reference'                                   => [
                'testMarker' => '/* testFunctionPassByReferenceD */',
                'expected'   => true,
            ],
            'reference: typed param in function declaration, pass by reference'                                 => [
                'testMarker' => '/* testFunctionPassByReferenceE */',
                'expected'   => true,
            ],
            'reference: typed param in closure declaration, pass by reference'                                  => [
                'testMarker' => '/* testFunctionPassByReferenceF */',
                'expected'   => true,
            ],
            'reference: variadic param in function declaration, pass by reference'                              => [
                'testMarker' => '/* testFunctionPassByReferenceG */',
                'expected'   => true,
            ],
            'reference: foreach value'                                                                          => [
                'testMarker' => '/* testForeachValueByReference */',
                'expected'   => true,
            ],
            'reference: foreach key'                                                                            => [
                'testMarker' => '/* testForeachKeyByReference */',
                'expected'   => true,
            ],
            'reference: keyed short array, first value, value by reference'                                     => [
                'testMarker' => '/* testArrayValueByReferenceA */',
                'expected'   => true,
            ],
            'reference: keyed short array, last value, value by reference'                                      => [
                'testMarker' => '/* testArrayValueByReferenceB */',
                'expected'   => true,
            ],
            'reference: unkeyed short array, only value, value by reference'                                    => [
                'testMarker' => '/* testArrayValueByReferenceC */',
                'expected'   => true,
            ],
            'reference: unkeyed short array, last value, value by reference'                                    => [
                'testMarker' => '/* testArrayValueByReferenceD */',
                'expected'   => true,
            ],
            'reference: keyed long array, first value, value by reference'                                      => [
                'testMarker' => '/* testArrayValueByReferenceE */',
                'expected'   => true,
            ],
            'reference: keyed long array, last value, value by reference'                                       => [
                'testMarker' => '/* testArrayValueByReferenceF */',
                'expected'   => true,
            ],
            'reference: unkeyed long array, only value, value by reference'                                     => [
                'testMarker' => '/* testArrayValueByReferenceG */',
                'expected'   => true,
            ],
            'reference: unkeyed long array, last value, value by reference'                                     => [
                'testMarker' => '/* testArrayValueByReferenceH */',
                'expected'   => true,
            ],
            'reference: variable, assign by reference'                                                          => [
                'testMarker' => '/* testAssignByReferenceA */',
                'expected'   => true,
            ],
            'reference: variable, assign by reference, spacing variation'                                       => [
                'testMarker' => '/* testAssignByReferenceB */',
                'expected'   => true,
            ],
            'reference: variable, assign by reference, concat assign'                                           => [
                'testMarker' => '/* testAssignByReferenceC */',
                'expected'   => true,
            ],
            'reference: property, assign by reference'                                                          => [
                'testMarker' => '/* testAssignByReferenceD */',
                'expected'   => true,
            ],
            'reference: function return value, assign by reference'                                             => [
                'testMarker' => '/* testAssignByReferenceE */',
                'expected'   => true,
            ],
            'reference: function return value, assign by reference, null coalesce assign'                       => [
                'testMarker' => '/* testAssignByReferenceF */',
                'expected'   => true,
            ],
            'reference: unkeyed short list, first var, assign by reference'                                     => [
                'testMarker' => '/* testShortListAssignByReferenceNoKeyA */',
                'expected'   => true,
            ],
            'reference: unkeyed short list, second var, assign by reference'                                    => [
                'testMarker' => '/* testShortListAssignByReferenceNoKeyB */',
                'expected'   => true,
            ],
            'reference: unkeyed short list, nested var, assign by reference'                                    => [
                'testMarker' => '/* testNestedShortListAssignByReferenceNoKey */',
                'expected'   => true,
            ],
            'reference: unkeyed long list, second var, assign by reference'                                     => [
                'testMarker' => '/* testLongListAssignByReferenceNoKeyA */',
                'expected'   => true,
            ],
            'reference: unkeyed long list, first nested var, assign by reference'                               => [
                'testMarker' => '/* testLongListAssignByReferenceNoKeyB */',
                'expected'   => true,
            ],
            'reference: unkeyed long list, last nested var, assign by reference'                                => [
                'testMarker' => '/* testLongListAssignByReferenceNoKeyC */',
                'expected'   => true,
            ],
            'reference: keyed short list, first nested var, assign by reference'                                => [
                'testMarker' => '/* testNestedShortListAssignByReferenceWithKeyA */',
                'expected'   => true,
            ],
            'reference: keyed short list, last nested var, assign by reference'                                 => [
                'testMarker' => '/* testNestedShortListAssignByReferenceWithKeyB */',
                'expected'   => true,
            ],
            'reference: keyed long list, only var, assign by reference'                                         => [
                'testMarker' => '/* testLongListAssignByReferenceWithKeyA */',
                'expected'   => true,
            ],
            'reference: first param in function call, pass by reference'                                        => [
                'testMarker' => '/* testPassByReferenceA */',
                'expected'   => true,
            ],
            'reference: last param in function call, pass by reference'                                         => [
                'testMarker' => '/* testPassByReferenceB */',
                'expected'   => true,
            ],
            'reference: property in function call, pass by reference'                                           => [
                'testMarker' => '/* testPassByReferenceC */',
                'expected'   => true,
            ],
            'reference: hierarchical self property in function call, pass by reference'                         => [
                'testMarker' => '/* testPassByReferenceD */',
                'expected'   => true,
            ],
            'reference: hierarchical parent property in function call, pass by reference'                       => [
                'testMarker' => '/* testPassByReferenceE */',
                'expected'   => true,
            ],
            'reference: hierarchical static property in function call, pass by reference'                       => [
                'testMarker' => '/* testPassByReferenceF */',
                'expected'   => true,
            ],
            'reference: static property in function call, pass by reference'                                    => [
                'testMarker' => '/* testPassByReferenceG */',
                'expected'   => true,
            ],
            'reference: static property in function call, first with FQN, pass by reference'                    => [
                'testMarker' => '/* testPassByReferenceH */',
                'expected'   => true,
            ],
            'reference: static property in function call, last with FQN, pass by reference'                     => [
                'testMarker' => '/* testPassByReferenceI */',
                'expected'   => true,
            ],
            'reference: static property in function call, last with namespace relative name, pass by reference' => [
                'testMarker' => '/* testPassByReferenceJ */',
                'expected'   => true,
            ],
            'reference: static property in function call, last with PQN, pass by reference'                     => [
                'testMarker' => '/* testPassByReferencePartiallyQualifiedName */',
                'expected'   => true,
            ],
            'reference: new by reference'                                                                       => [
                'testMarker' => '/* testNewByReferenceA */',
                'expected'   => true,
            ],
            'reference: new by reference as function call param'                                                => [
                'testMarker' => '/* testNewByReferenceB */',
                'expected'   => true,
            ],
            'reference: closure use by reference'                                                               => [
                'testMarker' => '/* testUseByReference */',
                'expected'   => true,
            ],
            'reference: closure use by reference, first param, with comment'                                    => [
                'testMarker' => '/* testUseByReferenceWithCommentFirstParam */',
                'expected'   => true,
            ],
            'reference: closure use by reference, last param, with comment'                                     => [
                'testMarker' => '/* testUseByReferenceWithCommentSecondParam */',
                'expected'   => true,
            ],
            'reference: arrow fn declared to return by reference'                                               => [
                'testMarker' => '/* testArrowFunctionReturnByReference */',
                'expected'   => true,
            ],
            'bitwise and: first param default value in closure declaration'                                     => [
                'testMarker' => '/* testBitwiseAndExactParameterA */',
                'expected'   => false,
            ],
            'reference: param in closure declaration, pass by reference'                                        => [
                'testMarker' => '/* testPassByReferenceExactParameterB */',
                'expected'   => true,
            ],
            'reference: variadic param in closure declaration, pass by reference'                               => [
                'testMarker' => '/* testPassByReferenceExactParameterC */',
                'expected'   => true,
            ],
            'bitwise and: last param default value in closure declaration'                                      => [
                'testMarker' => '/* testBitwiseAndExactParameterD */',
                'expected'   => false,
            ],
            'reference: typed param in arrow fn declaration, pass by reference'                                 => [
                'testMarker' => '/* testArrowFunctionPassByReferenceA */',
                'expected'   => true,
            ],
            'reference: variadic param in arrow fn declaration, pass by reference'                              => [
                'testMarker' => '/* testArrowFunctionPassByReferenceB */',
                'expected'   => true,
            ],
            'reference: closure declared to return by reference'                                                => [
                'testMarker' => '/* testClosureReturnByReference */',
                'expected'   => true,
            ],
            'bitwise and: param default value in arrow fn declaration'                                          => [
                'testMarker' => '/* testBitwiseAndArrowFunctionInDefault */',
                'expected'   => false,
            ],
            'reference: param pass by ref in arrow function'                                                    => [
                'testMarker' => '/* testParamPassByReference */',
                'expected'   => true,
            ],
            'issue-1284-short-list-directly-after-close-curly-control-structure'                                => [
                'testMarker' => '/* testTokenizerIssue1284PHPCSlt280A */',
                'expected'   => true,
            ],
            'issue-1284-short-list-directly-after-close-curly-control-structure-second-item'                    => [
                'testMarker' => '/* testTokenizerIssue1284PHPCSlt280B */',
                'expected'   => true,
            ],
            'issue-1284-short-array-directly-after-close-curly-control-structure'                               => [
                'testMarker' => '/* testTokenizerIssue1284PHPCSlt280C */',
                'expected'   => true,
            ],
        ];

    }//end dataIsReference()


}//end class
