* @copyright 2024 PHPCSStandards and contributors * @license https://github.com/PHPCSStandards/PHP_CodeSniffer/blob/master/licence.txt BSD Licence */ namespace PHP_CodeSniffer\Tests\Core\Tokenizer\Comment; /** * Tests that multiline docblocks are tokenized correctly. * * @covers PHP_CodeSniffer\Tokenizers\Comment */ final class MultiLineDocBlockTest extends CommentTestCase { /** * Data provider. * * @see testDocblockOpenerCloser() * * @return array>> */ public static function dataDocblockOpenerCloser() { return [ 'Multi line docblock: no contents' => [ 'marker' => '/* testEmptyDocblock */', 'closerOffset' => 3, 'expectedTags' => [], ], 'Multi line docblock: variety of text and tags' => [ 'marker' => '/* testMultilineDocblock */', 'closerOffset' => 95, // phpcs:ignore Squiz.Arrays.ArrayDeclaration.SingleLineNotAllowed 'expectedTags' => [21, 29, 36, 46, 56, 63, 73, 80, 90], ], 'Multi line docblock: no leading stars' => [ 'marker' => '/* testMultilineDocblockNoStars */', 'closerOffset' => 32, // phpcs:ignore Squiz.Arrays.ArrayDeclaration.SingleLineNotAllowed 'expectedTags' => [10, 16, 21, 27], ], 'Multi line docblock: indented' => [ 'marker' => '/* testMultilineDocblockIndented */', 'closerOffset' => 60, // phpcs:ignore Squiz.Arrays.ArrayDeclaration.SingleLineNotAllowed 'expectedTags' => [21, 28, 38, 45, 55], ], 'Multi line docblock: opener not on own line' => [ 'marker' => '/* testMultilineDocblockOpenerNotOnOwnLine */', 'closerOffset' => 10, 'expectedTags' => [], ], 'Multi line docblock: closer not on own line' => [ 'marker' => '/* testMultilineDocblockCloserNotOnOwnLine */', 'closerOffset' => 11, 'expectedTags' => [], ], 'Multi line docblock: stars not aligned' => [ 'marker' => '/* testMultilineDocblockStarsNotAligned */', 'closerOffset' => 26, 'expectedTags' => [], ], ]; }//end dataDocblockOpenerCloser() /** * Verify tokenization of an empty, multi-line DocBlock. * * @phpcs:disable Squiz.Arrays.ArrayDeclaration.SpaceBeforeDoubleArrow -- Readability is better with alignment. * * @return void */ public function testEmptyDocblock() { $expectedSequence = [ [T_DOC_COMMENT_OPEN_TAG => '/**'], [T_DOC_COMMENT_WHITESPACE => "\n"], [T_DOC_COMMENT_WHITESPACE => ' '], [T_DOC_COMMENT_CLOSE_TAG => '*/'], ]; $target = $this->getTargetToken('/* '.__FUNCTION__.' */', T_DOC_COMMENT_OPEN_TAG); $this->checkTokenSequence($target, $expectedSequence); }//end testEmptyDocblock() /** * Verify tokenization of a multi-line DocBlock containing all possible tokens. * * @return void */ public function testMultilineDocblock() { $expectedSequence = [ [T_DOC_COMMENT_OPEN_TAG => '/**'], [T_DOC_COMMENT_WHITESPACE => "\n"], [T_DOC_COMMENT_WHITESPACE => ' '], [T_DOC_COMMENT_STAR => '*'], [T_DOC_COMMENT_WHITESPACE => ' '], [T_DOC_COMMENT_STRING => 'This is a multi-line docblock.'], [T_DOC_COMMENT_WHITESPACE => "\n"], [T_DOC_COMMENT_WHITESPACE => ' '], [T_DOC_COMMENT_STAR => '*'], [T_DOC_COMMENT_WHITESPACE => "\n"], [T_DOC_COMMENT_WHITESPACE => ' '], [T_DOC_COMMENT_STAR => '*'], [T_DOC_COMMENT_WHITESPACE => ' '], [T_DOC_COMMENT_STRING => 'With blank lines, stars, tags, and tag descriptions.'], [T_DOC_COMMENT_WHITESPACE => "\n"], [T_DOC_COMMENT_WHITESPACE => ' '], [T_DOC_COMMENT_STAR => '*'], [T_DOC_COMMENT_WHITESPACE => "\n"], [T_DOC_COMMENT_WHITESPACE => ' '], [T_DOC_COMMENT_STAR => '*'], [T_DOC_COMMENT_WHITESPACE => ' '], [T_DOC_COMMENT_TAG => '@tagWithoutDescription'], [T_DOC_COMMENT_WHITESPACE => "\n"], [T_DOC_COMMENT_WHITESPACE => ' '], [T_DOC_COMMENT_STAR => '*'], [T_DOC_COMMENT_WHITESPACE => "\n"], [T_DOC_COMMENT_WHITESPACE => ' '], [T_DOC_COMMENT_STAR => '*'], [T_DOC_COMMENT_WHITESPACE => ' '], [T_DOC_COMMENT_TAG => '@since'], [T_DOC_COMMENT_WHITESPACE => ' '], [T_DOC_COMMENT_STRING => '10.3'], [T_DOC_COMMENT_WHITESPACE => "\n"], [T_DOC_COMMENT_WHITESPACE => ' '], [T_DOC_COMMENT_STAR => '*'], [T_DOC_COMMENT_WHITESPACE => ' '], [T_DOC_COMMENT_TAG => '@deprecated'], [T_DOC_COMMENT_WHITESPACE => ' '], [T_DOC_COMMENT_STRING => '11.5'], [T_DOC_COMMENT_WHITESPACE => "\n"], [T_DOC_COMMENT_WHITESPACE => ' '], [T_DOC_COMMENT_STAR => '*'], [T_DOC_COMMENT_WHITESPACE => "\n"], [T_DOC_COMMENT_WHITESPACE => ' '], [T_DOC_COMMENT_STAR => '*'], [T_DOC_COMMENT_WHITESPACE => ' '], [T_DOC_COMMENT_TAG => '@requires'], [T_DOC_COMMENT_WHITESPACE => ' '], [T_DOC_COMMENT_STRING => 'PHP 7.1 -- PHPUnit tag.'], [T_DOC_COMMENT_WHITESPACE => "\n"], [T_DOC_COMMENT_WHITESPACE => ' '], [T_DOC_COMMENT_STAR => '*'], [T_DOC_COMMENT_WHITESPACE => "\n"], [T_DOC_COMMENT_WHITESPACE => ' '], [T_DOC_COMMENT_STAR => '*'], [T_DOC_COMMENT_WHITESPACE => ' '], [T_DOC_COMMENT_TAG => '@tag-with-dashes-is-suppported'], [T_DOC_COMMENT_WHITESPACE => ' '], [T_DOC_COMMENT_STRING => 'Description.'], [T_DOC_COMMENT_WHITESPACE => "\n"], [T_DOC_COMMENT_WHITESPACE => ' '], [T_DOC_COMMENT_STAR => '*'], [T_DOC_COMMENT_WHITESPACE => ' '], [T_DOC_COMMENT_TAG => '@tag_with_underscores'], [T_DOC_COMMENT_WHITESPACE => ' '], [T_DOC_COMMENT_STRING => 'Description.'], [T_DOC_COMMENT_WHITESPACE => "\n"], [T_DOC_COMMENT_WHITESPACE => ' '], [T_DOC_COMMENT_STAR => '*'], [T_DOC_COMMENT_WHITESPACE => "\n"], [T_DOC_COMMENT_WHITESPACE => ' '], [T_DOC_COMMENT_STAR => '*'], [T_DOC_COMMENT_WHITESPACE => ' '], [T_DOC_COMMENT_TAG => '@param'], [T_DOC_COMMENT_WHITESPACE => ' '], [T_DOC_COMMENT_STRING => 'string $p1 Description 1.'], [T_DOC_COMMENT_WHITESPACE => "\n"], [T_DOC_COMMENT_WHITESPACE => ' '], [T_DOC_COMMENT_STAR => '*'], [T_DOC_COMMENT_WHITESPACE => ' '], [T_DOC_COMMENT_TAG => '@param'], [T_DOC_COMMENT_WHITESPACE => ' '], [T_DOC_COMMENT_STRING => 'int|false $p2 Description 2.'], [T_DOC_COMMENT_WHITESPACE => "\n"], [T_DOC_COMMENT_WHITESPACE => ' '], [T_DOC_COMMENT_STAR => '*'], [T_DOC_COMMENT_WHITESPACE => "\n"], [T_DOC_COMMENT_WHITESPACE => ' '], [T_DOC_COMMENT_STAR => '*'], [T_DOC_COMMENT_WHITESPACE => ' '], [T_DOC_COMMENT_TAG => '@return'], [T_DOC_COMMENT_WHITESPACE => ' '], [T_DOC_COMMENT_STRING => 'void'], [T_DOC_COMMENT_WHITESPACE => "\n"], [T_DOC_COMMENT_WHITESPACE => ' '], [T_DOC_COMMENT_CLOSE_TAG => '*/'], ]; $target = $this->getTargetToken('/* '.__FUNCTION__.' */', T_DOC_COMMENT_OPEN_TAG); $this->checkTokenSequence($target, $expectedSequence); }//end testMultilineDocblock() /** * Verify tokenization of a multi-line DocBlock with extra starts for the opener/closer and no stars on the lines between. * * @return void */ public function testMultilineDocblockNoStars() { $expectedSequence = [ [T_DOC_COMMENT_OPEN_TAG => '/****'], [T_DOC_COMMENT_WHITESPACE => "\n"], [T_DOC_COMMENT_WHITESPACE => ' '], [T_DOC_COMMENT_STRING => 'This is a multi-line docblock, but the lines are not marked with stars.'], [T_DOC_COMMENT_WHITESPACE => "\n"], [T_DOC_COMMENT_WHITESPACE => ' '], [T_DOC_COMMENT_STRING => 'Then again, the opener and closer have an abundance of stars.'], [T_DOC_COMMENT_WHITESPACE => "\n"], [T_DOC_COMMENT_WHITESPACE => "\n"], [T_DOC_COMMENT_WHITESPACE => ' '], [T_DOC_COMMENT_TAG => '@since'], [T_DOC_COMMENT_WHITESPACE => ' '], [T_DOC_COMMENT_STRING => '10.3'], [T_DOC_COMMENT_WHITESPACE => "\n"], [T_DOC_COMMENT_WHITESPACE => "\n"], [T_DOC_COMMENT_WHITESPACE => ' '], [T_DOC_COMMENT_TAG => '@param'], [T_DOC_COMMENT_WHITESPACE => ' '], [T_DOC_COMMENT_STRING => 'string $p1 Description 1.'], [T_DOC_COMMENT_WHITESPACE => "\n"], [T_DOC_COMMENT_WHITESPACE => ' '], [T_DOC_COMMENT_TAG => '@param'], [T_DOC_COMMENT_WHITESPACE => ' '], [T_DOC_COMMENT_STRING => 'int|false $p2 Description 2.'], [T_DOC_COMMENT_WHITESPACE => "\n"], [T_DOC_COMMENT_WHITESPACE => "\n"], [T_DOC_COMMENT_WHITESPACE => ' '], [T_DOC_COMMENT_TAG => '@return'], [T_DOC_COMMENT_WHITESPACE => ' '], [T_DOC_COMMENT_STRING => 'void'], [T_DOC_COMMENT_WHITESPACE => "\n"], [T_DOC_COMMENT_WHITESPACE => ' '], [T_DOC_COMMENT_CLOSE_TAG => '**/'], ]; $target = $this->getTargetToken('/* '.__FUNCTION__.' */', T_DOC_COMMENT_OPEN_TAG); $this->checkTokenSequence($target, $expectedSequence); }//end testMultilineDocblockNoStars() /** * Verify tokenization of a multi-line, indented DocBlock. * * @return void */ public function testMultilineDocblockIndented() { $expectedSequence = [ [T_DOC_COMMENT_OPEN_TAG => '/**'], [T_DOC_COMMENT_WHITESPACE => "\n"], [T_DOC_COMMENT_WHITESPACE => ' '], [T_DOC_COMMENT_STAR => '*'], [T_DOC_COMMENT_WHITESPACE => ' '], [T_DOC_COMMENT_STRING => 'This is a multi-line indented docblock.'], [T_DOC_COMMENT_WHITESPACE => "\n"], [T_DOC_COMMENT_WHITESPACE => ' '], [T_DOC_COMMENT_STAR => '*'], [T_DOC_COMMENT_WHITESPACE => "\n"], [T_DOC_COMMENT_WHITESPACE => ' '], [T_DOC_COMMENT_STAR => '*'], [T_DOC_COMMENT_WHITESPACE => ' '], [T_DOC_COMMENT_STRING => 'With blank lines, stars, tags, and tag descriptions.'], [T_DOC_COMMENT_WHITESPACE => "\n"], [T_DOC_COMMENT_WHITESPACE => ' '], [T_DOC_COMMENT_STAR => '*'], [T_DOC_COMMENT_WHITESPACE => "\n"], [T_DOC_COMMENT_WHITESPACE => ' '], [T_DOC_COMMENT_STAR => '*'], [T_DOC_COMMENT_WHITESPACE => ' '], [T_DOC_COMMENT_TAG => '@since'], [T_DOC_COMMENT_WHITESPACE => ' '], [T_DOC_COMMENT_STRING => '10.3'], [T_DOC_COMMENT_WHITESPACE => "\n"], [T_DOC_COMMENT_WHITESPACE => ' '], [T_DOC_COMMENT_STAR => '*'], [T_DOC_COMMENT_WHITESPACE => ' '], [T_DOC_COMMENT_TAG => '@deprecated'], [T_DOC_COMMENT_WHITESPACE => ' '], [T_DOC_COMMENT_STRING => '11.5'], [T_DOC_COMMENT_WHITESPACE => "\n"], [T_DOC_COMMENT_WHITESPACE => ' '], [T_DOC_COMMENT_STAR => '*'], [T_DOC_COMMENT_WHITESPACE => "\n"], [T_DOC_COMMENT_WHITESPACE => ' '], [T_DOC_COMMENT_STAR => '*'], [T_DOC_COMMENT_WHITESPACE => ' '], [T_DOC_COMMENT_TAG => '@param'], [T_DOC_COMMENT_WHITESPACE => ' '], [T_DOC_COMMENT_STRING => 'string $p1 Description 1.'], [T_DOC_COMMENT_WHITESPACE => "\n"], [T_DOC_COMMENT_WHITESPACE => ' '], [T_DOC_COMMENT_STAR => '*'], [T_DOC_COMMENT_WHITESPACE => ' '], [T_DOC_COMMENT_TAG => '@param'], [T_DOC_COMMENT_WHITESPACE => ' '], [T_DOC_COMMENT_STRING => 'int|false $p2 Description 2.'], [T_DOC_COMMENT_WHITESPACE => "\n"], [T_DOC_COMMENT_WHITESPACE => ' '], [T_DOC_COMMENT_STAR => '*'], [T_DOC_COMMENT_WHITESPACE => "\n"], [T_DOC_COMMENT_WHITESPACE => ' '], [T_DOC_COMMENT_STAR => '*'], [T_DOC_COMMENT_WHITESPACE => ' '], [T_DOC_COMMENT_TAG => '@return'], [T_DOC_COMMENT_WHITESPACE => ' '], [T_DOC_COMMENT_STRING => 'void'], [T_DOC_COMMENT_WHITESPACE => "\n"], [T_DOC_COMMENT_WHITESPACE => ' '], [T_DOC_COMMENT_CLOSE_TAG => '*/'], ]; $target = $this->getTargetToken('/* '.__FUNCTION__.' */', T_DOC_COMMENT_OPEN_TAG); $this->checkTokenSequence($target, $expectedSequence); }//end testMultilineDocblockIndented() /** * Verify tokenization of a multi-line DocBlock, where the opener is not on its own line. * * @return void */ public function testMultilineDocblockOpenerNotOnOwnLine() { $expectedSequence = [ [T_DOC_COMMENT_OPEN_TAG => '/**'], [T_DOC_COMMENT_WHITESPACE => ' '], [T_DOC_COMMENT_STRING => 'Start of description'], [T_DOC_COMMENT_WHITESPACE => "\n"], [T_DOC_COMMENT_WHITESPACE => ' '], [T_DOC_COMMENT_STAR => '*'], [T_DOC_COMMENT_WHITESPACE => ' '], [T_DOC_COMMENT_STRING => 'description continued.'], [T_DOC_COMMENT_WHITESPACE => "\n"], [T_DOC_COMMENT_WHITESPACE => ' '], [T_DOC_COMMENT_CLOSE_TAG => '*/'], ]; $target = $this->getTargetToken('/* '.__FUNCTION__.' */', T_DOC_COMMENT_OPEN_TAG); $this->checkTokenSequence($target, $expectedSequence); }//end testMultilineDocblockOpenerNotOnOwnLine() /** * Verify tokenization of a multi-line DocBlock, where the closer is not on its own line. * * @return void */ public function testMultilineDocblockCloserNotOnOwnLine() { $expectedSequence = [ [T_DOC_COMMENT_OPEN_TAG => '/**'], [T_DOC_COMMENT_WHITESPACE => "\n"], [T_DOC_COMMENT_WHITESPACE => ' '], [T_DOC_COMMENT_STAR => '*'], [T_DOC_COMMENT_WHITESPACE => ' '], [T_DOC_COMMENT_STRING => 'Start of description'], [T_DOC_COMMENT_WHITESPACE => "\n"], [T_DOC_COMMENT_WHITESPACE => ' '], [T_DOC_COMMENT_STAR => '*'], [T_DOC_COMMENT_WHITESPACE => ' '], [T_DOC_COMMENT_STRING => 'description continued. '], [T_DOC_COMMENT_CLOSE_TAG => '*/'], ]; $target = $this->getTargetToken('/* '.__FUNCTION__.' */', T_DOC_COMMENT_OPEN_TAG); $this->checkTokenSequence($target, $expectedSequence); }//end testMultilineDocblockCloserNotOnOwnLine() /** * Verify tokenization of a multi-line DocBlock with inconsistent indentation. * * @return void */ public function testMultilineDocblockStarsNotAligned() { $expectedSequence = [ [T_DOC_COMMENT_OPEN_TAG => '/**'], [T_DOC_COMMENT_WHITESPACE => "\n"], [T_DOC_COMMENT_STAR => '*'], [T_DOC_COMMENT_WHITESPACE => ' '], [T_DOC_COMMENT_STRING => 'Start of description.'], [T_DOC_COMMENT_WHITESPACE => "\n"], [T_DOC_COMMENT_STAR => '*'], [T_DOC_COMMENT_WHITESPACE => ' '], [T_DOC_COMMENT_STRING => 'Line below this is missing a star.'], [T_DOC_COMMENT_WHITESPACE => "\n"], [T_DOC_COMMENT_WHITESPACE => "\n"], [T_DOC_COMMENT_WHITESPACE => ' '], [T_DOC_COMMENT_STRING => 'Text'], [T_DOC_COMMENT_WHITESPACE => "\n"], [T_DOC_COMMENT_WHITESPACE => "\n"], [T_DOC_COMMENT_WHITESPACE => ' '], [T_DOC_COMMENT_STAR => '*'], [T_DOC_COMMENT_WHITESPACE => ' '], [T_DOC_COMMENT_STRING => 'Star indented.'], [T_DOC_COMMENT_WHITESPACE => "\n"], [T_DOC_COMMENT_WHITESPACE => ' '], [T_DOC_COMMENT_STAR => '*'], [T_DOC_COMMENT_WHITESPACE => ' '], [T_DOC_COMMENT_STRING => 'Closer indented.'], [T_DOC_COMMENT_WHITESPACE => "\n"], [T_DOC_COMMENT_WHITESPACE => ' '], [T_DOC_COMMENT_CLOSE_TAG => '*/'], ]; $target = $this->getTargetToken('/* '.__FUNCTION__.' */', T_DOC_COMMENT_OPEN_TAG); $this->checkTokenSequence($target, $expectedSequence); }//end testMultilineDocblockStarsNotAligned() }//end class