* Dariusz RumiƄski * * This source file is subject to the MIT license that is bundled * with this source code in the file LICENSE. */ namespace PhpCsFixer\Tests\Tokenizer\Analyzer; use PhpCsFixer\Tests\TestCase; use PhpCsFixer\Tokenizer\Analyzer\Analysis\ArgumentAnalysis; use PhpCsFixer\Tokenizer\Analyzer\Analysis\TypeAnalysis; use PhpCsFixer\Tokenizer\Analyzer\FunctionsAnalyzer; use PhpCsFixer\Tokenizer\Tokens; /** * @author VeeWee * * @internal * * @covers \PhpCsFixer\Tokenizer\Analyzer\FunctionsAnalyzer */ final class FunctionsAnalyzerTest extends TestCase { /** * @param list $indices * * @dataProvider provideIsGlobalFunctionCallCases */ public function testIsGlobalFunctionCall(string $code, array $indices): void { self::assertIsGlobalFunctionCall($indices, $code); } /** * @return iterable}> */ public static function provideIsGlobalFunctionCallCases(): iterable { yield [ 'bar("baz");', [], ]; yield [ ' [ ' [ ' false;', [], ]; yield [ ' $indices * * @dataProvider provideIsGlobalFunctionCallPre80Cases * * @requires PHP <8.0 */ public function testIsGlobalFunctionCallPre80(string $code, array $indices): void { self::assertIsGlobalFunctionCall($indices, $code); } /** * @return iterable}> */ public static function provideIsGlobalFunctionCallPre80Cases(): iterable { yield [ ' $indices * * @dataProvider provideIsGlobalFunctionCallPhp80Cases * * @requires PHP 8.0 */ public function testIsGlobalFunctionCallPhp80(string $code, array $indices): void { self::assertIsGlobalFunctionCall($indices, $code); } public static function provideIsGlobalFunctionCallPhp80Cases(): iterable { yield [ 'count();', [], ]; yield [ ' $indices * * @dataProvider provideIsGlobalFunctionCallPhp81Cases * * @requires PHP 8.1 */ public function testIsGlobalFunctionCallPhp81(array $indices, string $code): void { self::assertIsGlobalFunctionCall($indices, $code); } public static function provideIsGlobalFunctionCallPhp81Cases(): iterable { yield 'first class callable cases' => [ [], 'method(...); $obj->$methodStr(...); ($obj->property)(...); Foo::method(...); $classStr::$methodStr(...); self::{$complex . $expression}(...); \'strlen\'(...); [$obj, \'method\'](...); [Foo::class, \'method\'](...); $c = new class{}; $b = new class(){}; $a = new #[foo] class(){}; ', ]; yield [ [1, 20], ' $expected * * @dataProvider provideFunctionArgumentInfoCases */ public function testFunctionArgumentInfo(string $code, int $methodIndex, array $expected): void { $tokens = Tokens::fromCode($code); $analyzer = new FunctionsAnalyzer(); self::assertSame(serialize($expected), serialize($analyzer->getFunctionArguments($tokens, $methodIndex))); } /** * @return iterable}> */ public static function provideFunctionArgumentInfoCases(): iterable { yield [' new ArgumentAnalysis( '$a', 3, null, null ), ]]; yield [' new ArgumentAnalysis( '$a', 3, null, null ), '$b' => new ArgumentAnalysis( '$b', 6, null, null ), ]]; yield [' new ArgumentAnalysis( '$a', 3, null, null ), '$b' => new ArgumentAnalysis( '$b', 6, 'array(1,2)', null ), '$c' => new ArgumentAnalysis( '$c', 18, '3', null ), ]]; yield [' new ArgumentAnalysis( '$a', 5, 'array()', new TypeAnalysis( 'array', 3, 3 ) ), ]]; yield [' new ArgumentAnalysis( '$a', 7, null, new TypeAnalysis( 'array', 3, 3 ) ), ]]; yield [' new ArgumentAnalysis( '$a', 8, null, new TypeAnalysis( '\Foo\Bar', 3, 6 ) ), ]]; yield [' null;', 1, []]; yield [' null;', 1, [ '$a' => new ArgumentAnalysis( '$a', 3, null, null ), ]]; yield [' null;', 1, [ '$a' => new ArgumentAnalysis( '$a', 3, null, null ), '$b' => new ArgumentAnalysis( '$b', 6, null, null ), ]]; yield [' null;', 1, [ '$a' => new ArgumentAnalysis( '$a', 3, null, null ), '$b' => new ArgumentAnalysis( '$b', 6, 'array(1,2)', null ), '$c' => new ArgumentAnalysis( '$c', 18, '3', null ), ]]; yield [' null;', 1, [ '$a' => new ArgumentAnalysis( '$a', 5, 'array()', new TypeAnalysis( 'array', 3, 3 ) ), ]]; yield [' null;', 1, [ '$a' => new ArgumentAnalysis( '$a', 7, null, new TypeAnalysis( 'array', 3, 3 ) ), ]]; yield [' null;', 1, [ '$a' => new ArgumentAnalysis( '$a', 8, null, new TypeAnalysis( '\Foo\Bar', 3, 6 ) ), ]]; } /** * @param array $expected * * @dataProvider provideFunctionArgumentInfoPre80Cases * * @requires PHP <8.0 */ public function testFunctionArgumentInfoPre80(string $code, int $methodIndex, array $expected): void { $this->testFunctionArgumentInfo($code, $methodIndex, $expected); } /** * @return iterable}> */ public static function provideFunctionArgumentInfoPre80Cases(): iterable { yield [' null;', 1, [ '$a' => new ArgumentAnalysis( '$a', 9, null, new TypeAnalysis( '\Foo\Bar', 3, 7 ) ), ]]; yield [' new ArgumentAnalysis( '$a', 9, null, new TypeAnalysis( '\Foo\Bar', 3, 7 ) ), ]]; } /** * @dataProvider provideFunctionReturnTypeInfoCases */ public function testFunctionReturnTypeInfo(string $code, int $methodIndex, ?TypeAnalysis $expected): void { $tokens = Tokens::fromCode($code); $analyzer = new FunctionsAnalyzer(); $actual = $analyzer->getFunctionReturnType($tokens, $methodIndex); self::assertSame(serialize($expected), serialize($actual)); } /** * @return iterable */ public static function provideFunctionReturnTypeInfoCases(): iterable { yield [' null;', 1, null]; yield [' null;', 1, null]; yield [' null;', 1, new TypeAnalysis('array', 7, 7)]; yield [' null;', 1, new TypeAnalysis('\Foo\Bar', 7, 10)]; yield [' null;', 1, new TypeAnalysis('array', 8, 8)]; } /** * @dataProvider provideFunctionReturnTypeInfoPre80Cases * * @requires PHP <8.0 */ public function testFunctionReturnTypeInfoPre80(string $code, int $methodIndex, ?TypeAnalysis $expected): void { $this->testFunctionReturnTypeInfo($code, $methodIndex, $expected); } /** * @return iterable */ public static function provideFunctionReturnTypeInfoPre80Cases(): iterable { yield [' null;', 1, new TypeAnalysis('\Foo\Bar', 7, 11)]; } public function testIsTheSameClassCallInvalidIndex(): void { $tokens = Tokens::fromCode('expectException(\InvalidArgumentException::class); $this->expectExceptionMessage('Token index 666 does not exist.'); $analyzer->isTheSameClassCall($tokens, 666); } /** * @dataProvider provideIsTheSameClassCallCases * * @param list $sameClassCallIndices */ public function testIsTheSameClassCall(string $code, array $sameClassCallIndices): void { $tokens = Tokens::fromCode($code); $analyzer = new FunctionsAnalyzer(); for ($index = $tokens->count() - 1; $index >= 0; --$index) { self::assertSame( \in_array($index, $sameClassCallIndices, true), $analyzer->isTheSameClassCall($tokens, $index), \sprintf('Index %d failed check.', $index) ); } } /** * @return iterable}> */ public static function provideIsTheSameClassCallCases(): iterable { $template = ''), [24], ]; yield [ \sprintf($template, 'self::'), [24], ]; yield [ \sprintf($template, 'static::'), [24], ]; yield [ \sprintf($template, '$THIS->'), [24], ]; yield [ \sprintf($template, '$notThis->'), [], ]; yield [ \sprintf($template, 'Bar::'), [], ]; yield [ \sprintf($template, '$this::'), [24], ]; yield [ <<<'PHP' bar; } } PHP, [], ]; } /** * @dataProvider provideIsTheSameClassCall80Cases * * @param list $sameClassCallIndices * * @requires PHP 8.0 */ public function testIsTheSameClassCall80(string $code, array $sameClassCallIndices): void { $this->testIsTheSameClassCall($code, $sameClassCallIndices); } /** * @return iterable}> */ public static function provideIsTheSameClassCall80Cases(): iterable { yield [ 'otherMethod(1, 2, 3); } } ', [24], ]; } /** * @param array $expected * * @dataProvider provideFunctionArgumentInfoPhp80Cases * * @requires PHP 8.0 */ public function testFunctionArgumentInfoPhp80(string $code, int $methodIndex, array $expected): void { $this->testFunctionArgumentInfo($code, $methodIndex, $expected); } public static function provideFunctionArgumentInfoPhp80Cases(): iterable { yield [' new ArgumentAnalysis( '$aa', 3, null, null ), ]]; yield [' null;', 1, [ '$a' => new ArgumentAnalysis( '$a', 3, null, null ), '$bc' => new ArgumentAnalysis( '$bc', 6, null, null ), ]]; } /** * @param list $expectedIndices */ private static function assertIsGlobalFunctionCall(array $expectedIndices, string $code): void { $tokens = Tokens::fromCode($code); $analyzer = new FunctionsAnalyzer(); $actualIndices = []; foreach ($tokens as $index => $token) { if ($analyzer->isGlobalFunctionCall($tokens, $index)) { $actualIndices[] = $index; } } self::assertSame( $expectedIndices, $actualIndices, \sprintf( 'Global function calls found at positions: [%s], expected at [%s].', implode(', ', $actualIndices), implode(', ', $expectedIndices) ) ); } }