AnnotationTest.php 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680
  1. <?php
  2. declare(strict_types=1);
  3. /*
  4. * This file is part of PHP CS Fixer.
  5. *
  6. * (c) Fabien Potencier <fabien@symfony.com>
  7. * Dariusz Rumiński <dariusz.ruminski@gmail.com>
  8. *
  9. * This source file is subject to the MIT license that is bundled
  10. * with this source code in the file LICENSE.
  11. */
  12. namespace PhpCsFixer\Tests\DocBlock;
  13. use PhpCsFixer\DocBlock\Annotation;
  14. use PhpCsFixer\DocBlock\DocBlock;
  15. use PhpCsFixer\DocBlock\Line;
  16. use PhpCsFixer\Tests\TestCase;
  17. use PhpCsFixer\Tokenizer\Analyzer\Analysis\NamespaceAnalysis;
  18. use PhpCsFixer\Tokenizer\Analyzer\Analysis\NamespaceUseAnalysis;
  19. /**
  20. * @author Graham Campbell <hello@gjcampbell.co.uk>
  21. * @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
  22. *
  23. * @internal
  24. *
  25. * @covers \PhpCsFixer\DocBlock\Annotation
  26. */
  27. final class AnnotationTest extends TestCase
  28. {
  29. /**
  30. * This represents the content an entire docblock.
  31. *
  32. * @var string
  33. */
  34. private static $sample = '/**
  35. * Test docblock.
  36. *
  37. * @param string $hello
  38. * @param bool $test Description
  39. * extends over many lines
  40. *
  41. * @param adkjbadjasbdand $asdnjkasd
  42. *
  43. * @throws \Exception asdnjkasd
  44. *
  45. * asdasdasdasdasdasdasdasd
  46. * kasdkasdkbasdasdasdjhbasdhbasjdbjasbdjhb
  47. *
  48. * @return void
  49. */';
  50. /**
  51. * This represents the content of each annotation.
  52. *
  53. * @var string[]
  54. */
  55. private static $content = [
  56. " * @param string \$hello\n",
  57. " * @param bool \$test Description\n * extends over many lines\n",
  58. " * @param adkjbadjasbdand \$asdnjkasd\n",
  59. " * @throws \\Exception asdnjkasd\n *\n * asdasdasdasdasdasdasdasd\n * kasdkasdkbasdasdasdjhbasdhbasjdbjasbdjhb\n",
  60. " * @return void\n",
  61. ];
  62. /**
  63. * This represents the start indexes of each annotation.
  64. *
  65. * @var int[]
  66. */
  67. private static $start = [3, 4, 7, 9, 14];
  68. /**
  69. * This represents the start indexes of each annotation.
  70. *
  71. * @var int[]
  72. */
  73. private static $end = [3, 5, 7, 12, 14];
  74. /**
  75. * This represents the tag type of each annotation.
  76. *
  77. * @var string[]
  78. */
  79. private static $tags = ['param', 'param', 'param', 'throws', 'return'];
  80. /**
  81. * @dataProvider provideGetContentCases
  82. */
  83. public function testGetContent(int $index, string $content): void
  84. {
  85. $doc = new DocBlock(self::$sample);
  86. $annotation = $doc->getAnnotation($index);
  87. self::assertSame($content, $annotation->getContent());
  88. self::assertSame($content, (string) $annotation);
  89. }
  90. public static function provideGetContentCases(): iterable
  91. {
  92. foreach (self::$content as $index => $content) {
  93. yield [$index, $content];
  94. }
  95. }
  96. /**
  97. * @dataProvider provideStartCases
  98. */
  99. public function testStart(int $index, int $start): void
  100. {
  101. $doc = new DocBlock(self::$sample);
  102. $annotation = $doc->getAnnotation($index);
  103. self::assertSame($start, $annotation->getStart());
  104. }
  105. public static function provideStartCases(): iterable
  106. {
  107. foreach (self::$start as $index => $start) {
  108. yield [$index, $start];
  109. }
  110. }
  111. /**
  112. * @dataProvider provideEndCases
  113. */
  114. public function testEnd(int $index, int $end): void
  115. {
  116. $doc = new DocBlock(self::$sample);
  117. $annotation = $doc->getAnnotation($index);
  118. self::assertSame($end, $annotation->getEnd());
  119. }
  120. public static function provideEndCases(): iterable
  121. {
  122. foreach (self::$end as $index => $end) {
  123. yield [$index, $end];
  124. }
  125. }
  126. /**
  127. * @dataProvider provideGetTagCases
  128. */
  129. public function testGetTag(int $index, string $tag): void
  130. {
  131. $doc = new DocBlock(self::$sample);
  132. $annotation = $doc->getAnnotation($index);
  133. self::assertSame($tag, $annotation->getTag()->getName());
  134. }
  135. public static function provideGetTagCases(): iterable
  136. {
  137. foreach (self::$tags as $index => $tag) {
  138. yield [$index, $tag];
  139. }
  140. }
  141. /**
  142. * @dataProvider provideRemoveCases
  143. */
  144. public function testRemove(int $index, int $start, int $end): void
  145. {
  146. $doc = new DocBlock(self::$sample);
  147. $annotation = $doc->getAnnotation($index);
  148. $annotation->remove();
  149. self::assertSame('', $annotation->getContent());
  150. self::assertSame('', $doc->getLine($start)->getContent());
  151. self::assertSame('', $doc->getLine($end)->getContent());
  152. }
  153. public static function provideRemoveCases(): iterable
  154. {
  155. foreach (self::$start as $index => $start) {
  156. yield [$index, $start, self::$end[$index]];
  157. }
  158. }
  159. /**
  160. * @dataProvider provideRemoveEdgeCasesCases
  161. */
  162. public function testRemoveEdgeCases(string $expected, string $input): void
  163. {
  164. $doc = new DocBlock($input);
  165. $annotation = $doc->getAnnotation(0);
  166. $annotation->remove();
  167. self::assertSame($expected, $doc->getContent());
  168. }
  169. public static function provideRemoveEdgeCasesCases(): iterable
  170. {
  171. // Single line
  172. yield ['', '/** @return null*/'];
  173. yield ['', '/** @return null */'];
  174. yield ['', '/** @return null */'];
  175. // Multi line, annotation on start line
  176. yield [
  177. '/**
  178. */',
  179. '/** @return null
  180. */',
  181. ];
  182. yield [
  183. '/**
  184. */',
  185. '/** @return null '.'
  186. */',
  187. ];
  188. // Multi line, annotation on end line
  189. yield [
  190. '/**
  191. */',
  192. '/**
  193. * @return null*/',
  194. ];
  195. yield [
  196. '/**
  197. */',
  198. '/**
  199. * @return null */',
  200. ];
  201. }
  202. /**
  203. * @param string[] $expected
  204. *
  205. * @dataProvider provideTypeParsingCases
  206. */
  207. public function testTypeParsing(array $expected, string $input): void
  208. {
  209. $tag = new Annotation([new Line($input)]);
  210. self::assertSame($expected, $tag->getTypes());
  211. }
  212. public static function provideTypeParsingCases(): iterable
  213. {
  214. yield [
  215. ['int'],
  216. ' * @method int method()',
  217. ];
  218. yield [
  219. ['int[]'],
  220. " * @return int[]\r",
  221. ];
  222. yield [
  223. ['int[]'],
  224. " * @return int[]\r\n",
  225. ];
  226. yield [
  227. ['Foo[][]'],
  228. ' * @method Foo[][] method()',
  229. ];
  230. yield [
  231. ['int[]'],
  232. ' * @method int[] method()',
  233. ];
  234. yield [
  235. ['int[]', 'null'],
  236. ' * @method int[]|null method()',
  237. ];
  238. yield [
  239. ['int[]', 'null', '?int', 'array'],
  240. ' * @method int[]|null|?int|array method()',
  241. ];
  242. yield [
  243. ['null', 'Foo\Bar', '\Baz\Bax', 'int[]'],
  244. ' * @method null|Foo\Bar|\Baz\Bax|int[] method()',
  245. ];
  246. yield [
  247. ['gen<int>'],
  248. ' * @method gen<int> method()',
  249. ];
  250. yield [
  251. ['int', 'gen<int>'],
  252. ' * @method int|gen<int> method()',
  253. ];
  254. yield [
  255. ['\int', '\gen<\int, \bool>'],
  256. ' * @method \int|\gen<\int, \bool> method()',
  257. ];
  258. yield [
  259. ['gen<int, int>'],
  260. ' * @method gen<int, int> method()',
  261. ];
  262. yield [
  263. ['gen<int, bool|string>'],
  264. ' * @method gen<int, bool|string> method()',
  265. ];
  266. yield [
  267. ['gen<int, string[]>'],
  268. ' * @method gen<int, string[]> method() <> a',
  269. ];
  270. yield [
  271. ['gen<int, gener<string, bool>>'],
  272. ' * @method gen<int, gener<string, bool>> method() foo <a >',
  273. ];
  274. yield [
  275. ['gen<int, gener<string, null|bool>>'],
  276. ' * @method gen<int, gener<string, null|bool>> method()',
  277. ];
  278. yield [
  279. ['null', 'gen<int, gener<string, bool>>', 'int', 'string[]'],
  280. ' * @method null|gen<int, gener<string, bool>>|int|string[] method() foo <a >',
  281. ];
  282. yield [
  283. ['null', 'gen<int, gener<string, bool>>', 'int', 'array<int, string>', 'string[]'],
  284. ' * @method null|gen<int, gener<string, bool>>|int|array<int, string>|string[] method() foo <a >',
  285. ];
  286. yield [
  287. ['this'],
  288. '/** @return this */',
  289. ];
  290. yield [
  291. ['@this'],
  292. '/** @return @this */',
  293. ];
  294. yield [
  295. ['$SELF', 'int'],
  296. '/** @return $SELF|int */',
  297. ];
  298. yield [
  299. ['array<string|int, string>'],
  300. '/** @var array<string|int, string>',
  301. ];
  302. yield [
  303. ['int'],
  304. " * @return int\n",
  305. ];
  306. yield [
  307. ['int'],
  308. " * @return int\r\n",
  309. ];
  310. yield [
  311. ['Collection<Foo<Bar>, Foo<Baz>>'],
  312. '/** @var Collection<Foo<Bar>, Foo<Baz>>',
  313. ];
  314. yield [
  315. ['int', 'string'],
  316. '/** @var int | string',
  317. ];
  318. yield [
  319. ['Foo::*'],
  320. '/** @var Foo::*',
  321. ];
  322. yield [
  323. ['Foo::A'],
  324. '/** @var Foo::A',
  325. ];
  326. yield [
  327. ['Foo::A', 'Foo::B'],
  328. '/** @var Foo::A|Foo::B',
  329. ];
  330. yield [
  331. ['Foo::A*'],
  332. '/** @var Foo::A*',
  333. ];
  334. yield [
  335. ['array<Foo::A*>', 'null'],
  336. '/** @var array<Foo::A*>|null',
  337. ];
  338. yield [
  339. ['null', 'true', 'false', '1', '-1', '1.5', '-1.5', '.5', '1.', "'a'", '"b"'],
  340. '/** @var null|true|false|1|-1|1.5|-1.5|.5|1.|\'a\'|"b"',
  341. ];
  342. yield [
  343. ['int', '"a"', 'A<B<C, D>, E<F::*|G[]>>'],
  344. '/** @param int | "a" | A<B<C, D>, E<F::*|G[]>> $foo */',
  345. ];
  346. yield [
  347. ['class-string<Foo>'],
  348. '/** @var class-string<Foo> */',
  349. ];
  350. yield [
  351. ['A', 'B'],
  352. '/** @var A&B */',
  353. ];
  354. yield [
  355. ['A', 'B'],
  356. '/** @var A & B */',
  357. ];
  358. yield [
  359. ['array{1: bool, 2: bool}'],
  360. '/** @var array{1: bool, 2: bool} */',
  361. ];
  362. yield [
  363. ['array{a: int|string, b?: bool}'],
  364. '/** @var array{a: int|string, b?: bool} */',
  365. ];
  366. yield [
  367. ['array{\'a\': "a", "b"?: \'b\'}'],
  368. '/** @var array{\'a\': "a", "b"?: \'b\'} */',
  369. ];
  370. yield [
  371. ['array { a : int | string , b ? : A<B, C> }'],
  372. '/** @var array { a : int | string , b ? : A<B, C> } */',
  373. ];
  374. yield [
  375. ['callable(string)'],
  376. '/** @param callable(string) $function',
  377. ];
  378. yield [
  379. ['callable(string): bool'],
  380. '/** @param callable(string): bool $function',
  381. ];
  382. yield [
  383. ['callable(array<int, string>, array<int, Foo>): bool'],
  384. '/** @param callable(array<int, string>, array<int, Foo>): bool $function',
  385. ];
  386. yield [
  387. ['array<int, callable(string): bool>'],
  388. '/** @param array<int, callable(string): bool> $function',
  389. ];
  390. yield [
  391. ['callable(string): callable(int)'],
  392. '/** @param callable(string): callable(int) $function',
  393. ];
  394. yield [
  395. ['callable(string) : callable(int) : bool'],
  396. '/** @param callable(string) : callable(int) : bool $function',
  397. ];
  398. yield [
  399. ['TheCollection<callable(Foo, Bar,Baz): Foo[]>', 'string[]', 'null'],
  400. '* @param TheCollection<callable(Foo, Bar,Baz): Foo[]>|string[]|null $x',
  401. ];
  402. yield [
  403. ['Closure(string)'],
  404. '/** @param Closure(string) $function',
  405. ];
  406. yield [
  407. ['closure()'],
  408. '/** @param closure() $function',
  409. ];
  410. yield [
  411. ['array < int , callable ( string ) : bool >'],
  412. '/** @param array < int , callable ( string ) : bool > $function',
  413. ];
  414. }
  415. /**
  416. * @param string[] $expected
  417. * @param string[] $new
  418. *
  419. * @dataProvider provideTypesCases
  420. */
  421. public function testTypes(array $expected, array $new, string $input, string $output): void
  422. {
  423. $line = new Line($input);
  424. $tag = new Annotation([$line]);
  425. self::assertSame($expected, $tag->getTypes());
  426. $tag->setTypes($new);
  427. self::assertSame($new, $tag->getTypes());
  428. self::assertSame($output, $line->getContent());
  429. }
  430. public static function provideTypesCases(): iterable
  431. {
  432. yield [['Foo', 'null'], ['Bar[]'], ' * @param Foo|null $foo', ' * @param Bar[] $foo'];
  433. yield [['false'], ['bool'], '* @return false', '* @return bool'];
  434. yield [['RUNTIMEEEEeXCEPTION'], [\Throwable::class], "* \t@throws\t \t RUNTIMEEEEeXCEPTION\t\t\t\t\t\t\t\n\n\n", "* \t@throws\t \t Throwable\t\t\t\t\t\t\t\n\n\n"];
  435. yield [['RUNTIMEEEEeXCEPTION'], [\Throwable::class], "*\t@throws\t \t RUNTIMEEEEeXCEPTION\t\t\t\t\t\t\t\n\n\n", "*\t@throws\t \t Throwable\t\t\t\t\t\t\t\n\n\n"];
  436. yield [['RUNTIMEEEEeXCEPTION'], [\Throwable::class], "*@throws\t \t RUNTIMEEEEeXCEPTION\t\t\t\t\t\t\t\n\n\n", "*@throws\t \t Throwable\t\t\t\t\t\t\t\n\n\n"];
  437. yield [['string'], ['string', 'null'], ' * @method string getString()', ' * @method string|null getString()'];
  438. yield [['Foo', 'Bar'], ['Bar', 'Foo'], ' * @param Foo&Bar $x', ' * @param Bar&Foo $x'];
  439. }
  440. /**
  441. * @param string[] $expected
  442. *
  443. * @dataProvider provideNormalizedTypesCases
  444. */
  445. public function testNormalizedTypes(array $expected, string $input): void
  446. {
  447. $line = new Line($input);
  448. $tag = new Annotation([$line]);
  449. self::assertSame($expected, $tag->getNormalizedTypes());
  450. }
  451. public static function provideNormalizedTypesCases(): iterable
  452. {
  453. yield [['null', 'string'], '* @param StRiNg|NuLl $foo'];
  454. yield [['void'], '* @return Void'];
  455. yield [['bar', 'baz', 'foo', 'null', 'qux'], '* @return Foo|Bar|Baz|Qux|null'];
  456. yield [['bool', 'int'], '* @param bool|int $foo'];
  457. yield [['bool', 'int'], '* @param bool|int ...$foo'];
  458. yield [['bool', 'int'], '* @param bool|int &$foo'];
  459. yield [['bool', 'int'], '* @param bool|int &...$foo'];
  460. yield [['bool', 'int'], '* @param bool|int$foo'];
  461. yield [['bool', 'int'], '* @param bool|int&$foo'];
  462. yield [['bool', 'int'], '* @param bool|int&...$foo'];
  463. yield [['bar', 'baz', 'foo'], '* @param Foo|Bar&Baz&$param'];
  464. }
  465. public function testGetTypesOnBadTag(): void
  466. {
  467. $this->expectException(\RuntimeException::class);
  468. $this->expectExceptionMessage('This tag does not support types');
  469. $tag = new Annotation([new Line(' * @deprecated since Symfony 1.2')]);
  470. $tag->getTypes();
  471. }
  472. public function testSetTypesOnBadTag(): void
  473. {
  474. $this->expectException(\RuntimeException::class);
  475. $this->expectExceptionMessage('This tag does not support types');
  476. $tag = new Annotation([new Line(' * @author Chuck Norris')]);
  477. $tag->setTypes(['string']);
  478. }
  479. public function testGetTagsWithTypes(): void
  480. {
  481. $tags = Annotation::getTagsWithTypes();
  482. foreach ($tags as $tag) {
  483. self::assertIsString($tag);
  484. self::assertNotEmpty($tag);
  485. }
  486. }
  487. /**
  488. * @param NamespaceUseAnalysis[] $namespaceUses
  489. *
  490. * @dataProvider provideGetTypeExpressionCases
  491. */
  492. public function testGetTypeExpression(string $line, ?NamespaceAnalysis $namespace, array $namespaceUses, ?string $expectedCommonType): void
  493. {
  494. $annotation = new Annotation([new Line($line)], $namespace, $namespaceUses);
  495. $result = $annotation->getTypeExpression();
  496. self::assertSame($expectedCommonType, $result->getCommonType());
  497. }
  498. public static function provideGetTypeExpressionCases(): iterable
  499. {
  500. $appNamespace = new NamespaceAnalysis('App', 'App', 0, 999, 0, 999);
  501. $useTraversable = new NamespaceUseAnalysis('Traversable', 'Traversable', false, 0, 999, NamespaceUseAnalysis::TYPE_CLASS);
  502. yield ['* @param array|Traversable $foo', null, [], 'iterable'];
  503. yield ['* @param array|Traversable $foo', $appNamespace, [], null];
  504. yield ['* @param array|Traversable $foo', $appNamespace, [$useTraversable], 'iterable'];
  505. }
  506. /**
  507. * @dataProvider provideGetVariableNameCases
  508. */
  509. public function testGetVariableName(string $line, ?string $expectedVariableName): void
  510. {
  511. $annotation = new Annotation([new Line($line)]);
  512. self::assertSame($expectedVariableName, $annotation->getVariableName());
  513. }
  514. public static function provideGetVariableNameCases(): iterable
  515. {
  516. yield ['* @param int $foo', '$foo'];
  517. yield ['* @param int $foo some description', '$foo'];
  518. yield ['/** @param int $foo*/', '$foo'];
  519. yield ['* @param int', null];
  520. yield ['* @var int $foo', '$foo'];
  521. yield ['* @var int $foo some description', '$foo'];
  522. yield ['/** @var int $foo*/', '$foo'];
  523. yield ['* @var int', null];
  524. yield ['* @param $foo', '$foo'];
  525. yield ['* @param &$foo', '$foo'];
  526. yield ['* @param & $foo', '$foo'];
  527. yield ['* @param int &$foo', '$foo'];
  528. yield ['* @param int & $foo', '$foo'];
  529. yield ['* @param int ...$foo', '$foo'];
  530. yield ['* @param int ... $foo', '$foo'];
  531. yield ['* @param int&...$foo', '$foo'];
  532. yield ['* @param int &...$foo', '$foo'];
  533. yield ['* @param int & ...$foo', '$foo'];
  534. yield ['* @param int & ... $foo', '$foo'];
  535. }
  536. }