123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543 |
- <?php
- declare(strict_types=1);
- /*
- * This file is part of PHP CS Fixer.
- *
- * (c) Fabien Potencier <fabien@symfony.com>
- * Dariusz Rumiński <dariusz.ruminski@gmail.com>
- *
- * 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\CommentsAnalyzer;
- use PhpCsFixer\Tokenizer\Tokens;
- /**
- * @author Kuba Werłos <werlos@gmail.com>
- *
- * @internal
- *
- * @covers \PhpCsFixer\Tokenizer\Analyzer\CommentsAnalyzer
- */
- final class CommentsAnalyzerTest extends TestCase
- {
- public function testWhenNotPointingToComment(): void
- {
- $analyzer = new CommentsAnalyzer();
- $tokens = Tokens::fromCode('<?php $no; $comment; $here;');
- $this->expectException(\InvalidArgumentException::class);
- $this->expectExceptionMessage('Given index must point to a comment.');
- $analyzer->getCommentBlockIndices($tokens, 4);
- }
- /**
- * @param list<int> $borders
- *
- * @dataProvider provideCommentsCases
- */
- public function testComments(string $code, int $index, array $borders): void
- {
- $tokens = Tokens::fromCode($code);
- $analyzer = new CommentsAnalyzer();
- self::assertSame($borders, $analyzer->getCommentBlockIndices($tokens, $index));
- self::assertFalse($analyzer->isHeaderComment($tokens, $index));
- }
- public static function provideCommentsCases(): iterable
- {
- yield 'discover all 4 comments for the 1st comment with slash' => [
- '<?php
- $foo;
- // one
- // two
- // three
- // four
- $bar;',
- 4,
- [4, 6, 8, 10],
- ];
- yield 'discover all 4 comments for the 1st comment with hash' => [
- '<?php
- $foo;
- # one
- # two
- # three
- # four
- $bar;',
- 4,
- [4, 6, 8, 10],
- ];
- yield 'discover 3 comments out of 4 for the 2nd comment' => [
- '<?php
- $foo;
- // one
- // two
- // three
- // four
- $bar;',
- 6,
- [6, 8, 10],
- ];
- yield 'discover 3 comments when empty line separates 4th' => [
- '<?php
- $foo;
- // one
- // two
- // three
- // four
- $bar;',
- 4,
- [4, 6, 8],
- ];
- yield 'discover 3 comments when empty line of CR separates 4th' => [
- str_replace("\n", "\r", '<?php
- $foo;
- // one
- // two
- // three
- // four
- $bar;'),
- 4,
- [4, 6, 8],
- ];
- yield 'discover correctly when mix of slash and hash' => [
- '<?php
- $foo;
- // one
- // two
- # three
- // four
- $bar;',
- 4,
- [4, 6],
- ];
- yield 'do not group asterisk comments' => [
- '<?php
- $foo;
- /* one */
- /* two */
- /* three */
- $bar;',
- 4,
- [4],
- ];
- yield 'handle fancy indent' => [
- '<?php
- $foo;
- // one
- // two
- // three
- // four
- $bar;',
- 4,
- [4, 6, 8, 10],
- ];
- }
- public function testHeaderCommentAcceptsOnlyComments(): void
- {
- $tokens = Tokens::fromCode('<?php 1; 2; 3;');
- $analyzer = new CommentsAnalyzer();
- $this->expectException(\InvalidArgumentException::class);
- $analyzer->isHeaderComment($tokens, 2);
- }
- /**
- * @dataProvider provideHeaderCommentCases
- */
- public function testHeaderComment(string $code, int $index): void
- {
- $tokens = Tokens::fromCode($code);
- $analyzer = new CommentsAnalyzer();
- self::assertTrue($analyzer->isHeaderComment($tokens, $index));
- }
- /**
- * @return iterable<array{string, int}>
- */
- public static function provideHeaderCommentCases(): iterable
- {
- yield ['<?php /* Comment */ namespace Foo;', 1];
- yield ['<?php /** Comment */ namespace Foo;', 1];
- yield ['<?php declare(strict_types=1); /* Comment */ namespace Foo;', 9];
- yield ['<?php /* We test this one */ /* Foo */ namespace Bar;', 1];
- yield ['<?php /** Comment */ namespace Foo; declare(strict_types=1); /* Comment */ namespace Foo;', 1];
- }
- /**
- * @dataProvider provideNotHeaderCommentCases
- */
- public function testNotHeaderComment(string $code, int $index): void
- {
- $tokens = Tokens::fromCode($code);
- $analyzer = new CommentsAnalyzer();
- self::assertFalse($analyzer->isHeaderComment($tokens, $index));
- }
- /**
- * @return iterable<array{string, int}>
- */
- public static function provideNotHeaderCommentCases(): iterable
- {
- yield ['<?php $foo; /* Comment */ $bar;', 4];
- yield ['<?php foo(); /* Comment */ $bar;', 6];
- yield ['<?php namespace Foo; /* Comment */ class Bar {};', 6];
- yield ['<?php /* It is not header when no content after */', 1];
- yield ['<?php /* Foo */ /* We test this one */ namespace Bar;', 3];
- yield ['<?php /* Foo */ declare(strict_types=1); /* We test this one */ namespace Bar;', 11];
- }
- public function testPhpdocCandidateAcceptsOnlyComments(): void
- {
- $tokens = Tokens::fromCode('<?php 1; 2; 3;');
- $analyzer = new CommentsAnalyzer();
- $this->expectException(\InvalidArgumentException::class);
- $analyzer->isBeforeStructuralElement($tokens, 2);
- }
- /**
- * @dataProvider providePhpdocCandidateCases
- */
- public function testPhpdocCandidate(string $code): void
- {
- $tokens = Tokens::fromCode($code);
- $index = $tokens->getNextTokenOfKind(0, [[T_COMMENT], [T_DOC_COMMENT]]);
- $analyzer = new CommentsAnalyzer();
- self::assertTrue($analyzer->isBeforeStructuralElement($tokens, $index));
- }
- /**
- * @return iterable<array{string}>
- */
- public static function providePhpdocCandidateCases(): iterable
- {
- yield ['<?php /* @var Foo */ $bar = "baz";'];
- yield ['<?php /* Before namespace */ namespace Foo;'];
- yield ['<?php /* Before class */ class Foo {}'];
- yield ['<?php /* Before class */ abstract class Foo {}'];
- yield ['<?php /* Before class */ final class Foo {}'];
- yield ['<?php /* Before trait */ trait Foo {}'];
- yield ['<?php /* Before interface */ interface Foo {}'];
- yield ['<?php /* Before anonymous function */ function () {};'];
- yield ['<?php class Foo { /* Before property */ private $bar; }'];
- yield ['<?php class Foo { /* Before property */ protected $bar; }'];
- yield ['<?php class Foo { /* Before property */ public $bar; }'];
- yield ['<?php class Foo { /* Before property */ var $bar; }'];
- yield ['<?php class Foo { /* Before function */ function bar() {} }'];
- yield ['<?php class Foo { /* Before use */ use Bar; }'];
- yield ['<?php class Foo { /* Before function */ final function bar() {} }'];
- yield ['<?php class Foo { /* Before function */ private function bar() {} }'];
- yield ['<?php class Foo { /* Before function */ protected function bar() {} }'];
- yield ['<?php class Foo { /* Before function */ public function bar() {} }'];
- yield ['<?php class Foo { /* Before function */ static function bar() {} }'];
- yield ['<?php class Foo { /* Before function */ abstract function bar(); }'];
- yield ['<?php class Foo { /* Before constant */ const FOO = 42; }'];
- yield ['<?php /* Before require */ require "foo/php";'];
- yield ['<?php /* Before require_once */ require_once "foo/php";'];
- yield ['<?php /* Before include */ include "foo/php";'];
- yield ['<?php /* Before include_once */ include_once "foo/php";'];
- yield ['<?php /* @var array $foo */ foreach ($foo as $bar) {};'];
- yield ['<?php /* @var int $foo */ if ($foo === -1) {};'];
- yield ['<?php /* @var SomeClass $foo */ switch ($foo) { default: exit; };'];
- yield ['<?php /* @var bool $foo */ while ($foo) { $foo--; };'];
- yield ['<?php /* @var int $i */ for ($i = 0; $i < 16; $i++) {};'];
- yield ['<?php /* @var int $i @var int $j */ list($i, $j) = getValues();'];
- yield ['<?php /* @var string $s */ print($s);'];
- yield ['<?php /* @var string $s */ echo($s);'];
- yield ['<?php /* @var User $bar */ ($baz = tmp())->doSomething();'];
- yield ['<?php /* @var User $bar */ list($bar) = a();'];
- yield ['<?php /* Before anonymous function */ $fn = fn($x) => $x + 1;'];
- yield ['<?php /* Before anonymous function */ fn($x) => $x + 1;'];
- yield ['<?php /* @var int $x */ [$x] = [2];'];
- }
- /**
- * @dataProvider provideNotPhpdocCandidateCases
- */
- public function testNotPhpdocCandidate(string $code): void
- {
- $tokens = Tokens::fromCode($code);
- $index = $tokens->getNextTokenOfKind(0, [[T_COMMENT], [T_DOC_COMMENT]]);
- $analyzer = new CommentsAnalyzer();
- self::assertFalse($analyzer->isBeforeStructuralElement($tokens, $index));
- }
- /**
- * @return iterable<array{string}>
- */
- public static function provideNotPhpdocCandidateCases(): iterable
- {
- yield ['<?php class Foo {} /* At the end of file */'];
- yield ['<?php class Foo { public $baz; public function baz(); /* At the end of class */ }'];
- yield ['<?php /* Before increment */ $i++;'];
- yield ['<?php /* Comment, but not doc block */ if ($foo === -1) {};'];
- yield ['<?php
- $a = $b[1]; // @phpstan-ignore-line
- static::bar();',
- ];
- yield ['<?php /* @var int $a */ [$b] = [2];'];
- }
- /**
- * @dataProvider providePhpdocCandidatePhp80Cases
- *
- * @requires PHP 8.0
- */
- public function testPhpdocCandidatePhp80(string $code): void
- {
- $this->testPhpdocCandidate($code);
- }
- /**
- * @return iterable<string, array{string}>
- */
- public static function providePhpdocCandidatePhp80Cases(): iterable
- {
- yield 'attribute between class and phpDoc' => [
- '<?php
- /**
- * @Annotation
- */
- #[CustomAnnotationA]
- Class MyAnnotation3 {}',
- ];
- }
- /**
- * @dataProvider providePhpdocCandidatePhp81Cases
- *
- * @requires PHP 8.1
- */
- public function testPhpdocCandidatePhp81(string $code): void
- {
- $this->testPhpdocCandidate($code);
- }
- /**
- * @return iterable<string, array{string}>
- */
- public static function providePhpdocCandidatePhp81Cases(): iterable
- {
- yield 'public readonly' => [
- '<?php class Foo { /* */ public readonly int $a1; }',
- ];
- yield 'readonly public' => [
- '<?php class Foo { /* */ readonly public int $a1; }',
- ];
- yield 'readonly union' => [
- '<?php class Foo { /* */ readonly A|B $a1; }',
- ];
- yield 'public final const' => [
- '<?php final class Foo2 extends B implements A
- {
- /* */
- public final const Y = "i";
- }',
- ];
- yield 'final public const' => [
- '<?php final class Foo2 extends B implements A
- {
- /* */
- final public const Y = "i";
- }',
- ];
- yield 'enum' => [
- '<?php /* Before enum */ enum Foo {}',
- ];
- yield 'enum with deprecated case' => [
- '<?php
- enum Foo: int {
- /**
- * @deprecated Lorem ipsum
- */
- case BAR = 1;
- }',
- ];
- }
- /**
- * @dataProvider provideNotPhpdocCandidatePhp81Cases
- *
- * @requires PHP 8.1
- */
- public function testNotPhpdocCandidatePhp81(string $code): void
- {
- $this->testNotPhpdocCandidate($code);
- }
- /**
- * @return iterable<string, array{string}>
- */
- public static function provideNotPhpdocCandidatePhp81Cases(): iterable
- {
- yield 'enum and switch' => [
- '<?php
- enum E {}
- switch ($x) {
- /* */
- case 1: return 2;
- }
- ',
- ];
- yield 'switch and enum' => [
- '<?php
- switch ($x) {
- /* */
- case 1: return 2;
- }
- enum E {}
- ',
- ];
- }
- /**
- * @dataProvider provideReturnStatementCases
- */
- public function testReturnStatement(string $code, bool $expected): void
- {
- $tokens = Tokens::fromCode($code);
- $index = $tokens->getNextTokenOfKind(0, [[T_COMMENT], [T_DOC_COMMENT]]);
- $analyzer = new CommentsAnalyzer();
- self::assertSame($expected, $analyzer->isBeforeReturn($tokens, $index));
- }
- /**
- * @return iterable<string, array{string, bool}>
- */
- public static function provideReturnStatementCases(): iterable
- {
- yield 'docblock before var' => [
- '<?php
- function returnClassName()
- {
- /** @todo something */
- $var = 123;
- return;
- }
- ',
- false,
- ];
- yield 'comment before var' => [
- '<?php
- function returnClassName()
- {
- // @todo something
- $var = 123;
- return;
- }
- ',
- false,
- ];
- yield 'docblock return' => [
- '<?php
- function returnClassName()
- {
- /** @todo something */
- return;
- }
- ',
- true,
- ];
- yield 'comment return' => [
- '<?php
- function returnClassName()
- {
- // @todo something
- return;
- }
- ',
- true,
- ];
- }
- }
|