AnnotationTest.php 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577
  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. static::assertSame($content, $annotation->getContent());
  88. static::assertSame($content, (string) $annotation);
  89. }
  90. public function provideGetContentCases(): \Generator
  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. static::assertSame($start, $annotation->getStart());
  104. }
  105. public function provideStartCases(): \Generator
  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. static::assertSame($end, $annotation->getEnd());
  119. }
  120. public function provideEndCases(): \Generator
  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. static::assertSame($tag, $annotation->getTag()->getName());
  134. }
  135. public function provideGetTagCases(): \Generator
  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. static::assertSame('', $annotation->getContent());
  150. static::assertSame('', $doc->getLine($start)->getContent());
  151. static::assertSame('', $doc->getLine($end)->getContent());
  152. }
  153. public function provideRemoveCases(): \Generator
  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. static::assertSame($expected, $doc->getContent());
  168. }
  169. public function provideRemoveEdgeCasesCases(): array
  170. {
  171. return [
  172. // Single line
  173. ['', '/** @return null*/'],
  174. ['', '/** @return null */'],
  175. ['', '/** @return null */'],
  176. // Multi line, annotation on start line
  177. [
  178. '/**
  179. */',
  180. '/** @return null
  181. */',
  182. ],
  183. [
  184. '/**
  185. */',
  186. '/** @return null '.'
  187. */',
  188. ],
  189. // Multi line, annotation on end line
  190. [
  191. '/**
  192. */',
  193. '/**
  194. * @return null*/',
  195. ],
  196. [
  197. '/**
  198. */',
  199. '/**
  200. * @return null */',
  201. ],
  202. ];
  203. }
  204. /**
  205. * @param string[] $expected
  206. *
  207. * @dataProvider provideTypeParsingCases
  208. */
  209. public function testTypeParsing(array $expected, string $input): void
  210. {
  211. $tag = new Annotation([new Line($input)]);
  212. static::assertSame($expected, $tag->getTypes());
  213. }
  214. public function provideTypeParsingCases(): array
  215. {
  216. return [
  217. [
  218. ['int'],
  219. ' * @method int method()',
  220. ],
  221. [
  222. ['int[]'],
  223. " * @return int[]\r",
  224. ],
  225. [
  226. ['int[]'],
  227. " * @return int[]\r\n",
  228. ],
  229. [
  230. ['Foo[][]'],
  231. ' * @method Foo[][] method()',
  232. ],
  233. [
  234. ['int[]'],
  235. ' * @method int[] method()',
  236. ],
  237. [
  238. ['int[]', 'null'],
  239. ' * @method int[]|null method()',
  240. ],
  241. [
  242. ['int[]', 'null', '?int', 'array'],
  243. ' * @method int[]|null|?int|array method()',
  244. ],
  245. [
  246. ['null', 'Foo\Bar', '\Baz\Bax', 'int[]'],
  247. ' * @method null|Foo\Bar|\Baz\Bax|int[] method()',
  248. ],
  249. [
  250. ['gen<int>'],
  251. ' * @method gen<int> method()',
  252. ],
  253. [
  254. ['int', 'gen<int>'],
  255. ' * @method int|gen<int> method()',
  256. ],
  257. [
  258. ['\int', '\gen<\int, \bool>'],
  259. ' * @method \int|\gen<\int, \bool> method()',
  260. ],
  261. [
  262. ['gen<int, int>'],
  263. ' * @method gen<int, int> method()',
  264. ],
  265. [
  266. ['gen<int, bool|string>'],
  267. ' * @method gen<int, bool|string> method()',
  268. ],
  269. [
  270. ['gen<int, string[]>'],
  271. ' * @method gen<int, string[]> method() <> a',
  272. ],
  273. [
  274. ['gen<int, gener<string, bool>>'],
  275. ' * @method gen<int, gener<string, bool>> method() foo <a >',
  276. ],
  277. [
  278. ['gen<int, gener<string, null|bool>>'],
  279. ' * @method gen<int, gener<string, null|bool>> method()',
  280. ],
  281. [
  282. ['null', 'gen<int, gener<string, bool>>', 'int', 'string[]'],
  283. ' * @method null|gen<int, gener<string, bool>>|int|string[] method() foo <a >',
  284. ],
  285. [
  286. ['null', 'gen<int, gener<string, bool>>', 'int', 'array<int, string>', 'string[]'],
  287. ' * @method null|gen<int, gener<string, bool>>|int|array<int, string>|string[] method() foo <a >',
  288. ],
  289. [
  290. ['this'],
  291. '/** @return this */',
  292. ],
  293. [
  294. ['@this'],
  295. '/** @return @this */',
  296. ],
  297. [
  298. ['$SELF', 'int'],
  299. '/** @return $SELF|int */',
  300. ],
  301. [
  302. ['array<string|int, string>'],
  303. '/** @var array<string|int, string>',
  304. ],
  305. [
  306. ['int'],
  307. " * @return int\n",
  308. ],
  309. [
  310. ['int'],
  311. " * @return int\r\n",
  312. ],
  313. [
  314. ['Collection<Foo<Bar>, Foo<Baz>>'],
  315. '/** @var Collection<Foo<Bar>, Foo<Baz>>',
  316. ],
  317. [
  318. ['int', 'string'],
  319. '/** @var int | string',
  320. ],
  321. [
  322. ['Foo::*'],
  323. '/** @var Foo::*',
  324. ],
  325. [
  326. ['Foo::A'],
  327. '/** @var Foo::A',
  328. ],
  329. [
  330. ['Foo::A', 'Foo::B'],
  331. '/** @var Foo::A|Foo::B',
  332. ],
  333. [
  334. ['Foo::A*'],
  335. '/** @var Foo::A*',
  336. ],
  337. [
  338. ['array<Foo::A*>', 'null'],
  339. '/** @var array<Foo::A*>|null',
  340. ],
  341. [
  342. ['null', 'true', 'false', '1', '1.5', "'a'", '"b"'],
  343. '/** @var null|true|false|1|1.5|\'a\'|"b"',
  344. ],
  345. [
  346. ['int', '"a"', 'A<B<C, D>, E<F::*|G[]>>'],
  347. '/** @param int | "a" | A<B<C, D>, E<F::*|G[]>> $foo */',
  348. ],
  349. [
  350. ['class-string<Foo>'],
  351. '/** @var class-string<Foo> */',
  352. ],
  353. [
  354. ['A&B'],
  355. '/** @var A&B */',
  356. ],
  357. [
  358. ['A & B'],
  359. '/** @var A & B */',
  360. ],
  361. [
  362. ['array{1: bool, 2: bool}'],
  363. '/** @var array{1: bool, 2: bool} */',
  364. ],
  365. [
  366. ['array{a: int|string, b?: bool}'],
  367. '/** @var array{a: int|string, b?: bool} */',
  368. ],
  369. [
  370. ['array{\'a\': "a", "b"?: \'b\'}'],
  371. '/** @var array{\'a\': "a", "b"?: \'b\'} */',
  372. ],
  373. [
  374. ['array { a : int | string , b ? : A<B, C> }'],
  375. '/** @var array { a : int | string , b ? : A<B, C> } */',
  376. ],
  377. [
  378. ['callable(string)'],
  379. '/** @param callable(string) $function',
  380. ],
  381. [
  382. ['callable(string): bool'],
  383. '/** @param callable(string): bool $function',
  384. ],
  385. [
  386. ['callable(array<int, string>, array<int, Foo>): bool'],
  387. '/** @param callable(array<int, string>, array<int, Foo>): bool $function',
  388. ],
  389. [
  390. ['array<int, callable(string): bool>'],
  391. '/** @param array<int, callable(string): bool> $function',
  392. ],
  393. [
  394. ['callable(string): callable(int)'],
  395. '/** @param callable(string): callable(int) $function',
  396. ],
  397. [
  398. ['callable(string) : callable(int) : bool'],
  399. '/** @param callable(string) : callable(int) : bool $function',
  400. ],
  401. [
  402. ['TheCollection<callable(Foo, Bar,Baz): Foo[]>', 'string[]', 'null'],
  403. '* @param TheCollection<callable(Foo, Bar,Baz): Foo[]>|string[]|null $x',
  404. ],
  405. [
  406. ['Closure(string)'],
  407. '/** @param Closure(string) $function',
  408. ],
  409. [
  410. ['array < int , callable ( string ) : bool >'],
  411. '/** @param array < int , callable ( string ) : bool > $function',
  412. ],
  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. static::assertSame($expected, $tag->getTypes());
  426. $tag->setTypes($new);
  427. static::assertSame($new, $tag->getTypes());
  428. static::assertSame($output, $line->getContent());
  429. }
  430. public function provideTypesCases(): array
  431. {
  432. return [
  433. [['Foo', 'null'], ['Bar[]'], ' * @param Foo|null $foo', ' * @param Bar[] $foo'],
  434. [['false'], ['bool'], '* @return false', '* @return bool'],
  435. [['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. [['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"],
  437. [['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"],
  438. [['string'], ['string', 'null'], ' * @method string getString()', ' * @method string|null getString()'],
  439. ];
  440. }
  441. /**
  442. * @param string[] $expected
  443. *
  444. * @dataProvider provideNormalizedTypesCases
  445. */
  446. public function testNormalizedTypes(array $expected, string $input): void
  447. {
  448. $line = new Line($input);
  449. $tag = new Annotation([$line]);
  450. static::assertSame($expected, $tag->getNormalizedTypes());
  451. }
  452. public function provideNormalizedTypesCases(): array
  453. {
  454. return [
  455. [['null', 'string'], '* @param StRiNg|NuLl $foo'],
  456. [['void'], '* @return Void'],
  457. [['bar', 'baz', 'foo', 'null', 'qux'], '* @return Foo|Bar|Baz|Qux|null'],
  458. ];
  459. }
  460. public function testGetTypesOnBadTag(): void
  461. {
  462. $this->expectException(\RuntimeException::class);
  463. $this->expectExceptionMessage('This tag does not support types');
  464. $tag = new Annotation([new Line(' * @deprecated since 1.2')]);
  465. $tag->getTypes();
  466. }
  467. public function testSetTypesOnBadTag(): void
  468. {
  469. $this->expectException(\RuntimeException::class);
  470. $this->expectExceptionMessage('This tag does not support types');
  471. $tag = new Annotation([new Line(' * @author Chuck Norris')]);
  472. $tag->setTypes(['string']);
  473. }
  474. public function testGetTagsWithTypes(): void
  475. {
  476. $tags = Annotation::getTagsWithTypes();
  477. foreach ($tags as $tag) {
  478. static::assertIsString($tag);
  479. static::assertNotEmpty($tag);
  480. }
  481. }
  482. /**
  483. * @param Line[] $lines
  484. * @param NamespaceUseAnalysis[] $namespaceUses
  485. *
  486. * @dataProvider provideTypeExpressionCases
  487. */
  488. public function testGetTypeExpression(array $lines, ?NamespaceAnalysis $namespace, array $namespaceUses, ?string $expectedCommonType): void
  489. {
  490. $annotation = new Annotation($lines, $namespace, $namespaceUses);
  491. $result = $annotation->getTypeExpression();
  492. static::assertSame($expectedCommonType, $result->getCommonType());
  493. }
  494. public function provideTypeExpressionCases(): \Generator
  495. {
  496. $appNamespace = new NamespaceAnalysis('App', 'App', 0, 999, 0, 999);
  497. $useTraversable = new NamespaceUseAnalysis('Traversable', 'Traversable', false, 0, 999, NamespaceUseAnalysis::TYPE_CLASS);
  498. yield [[new Line('* @param array|Traversable $foo')], null, [], 'iterable'];
  499. yield [[new Line('* @param array|Traversable $foo')], $appNamespace, [], null];
  500. yield [[new Line('* @param array|Traversable $foo')], $appNamespace, [$useTraversable], 'iterable'];
  501. }
  502. /**
  503. * @param Line[] $lines
  504. *
  505. * @dataProvider provideGetVariableCases
  506. */
  507. public function testGetVariableName(array $lines, ?string $expectedVariableName): void
  508. {
  509. $annotation = new Annotation($lines);
  510. static::assertSame($expectedVariableName, $annotation->getVariableName());
  511. }
  512. public function provideGetVariableCases(): \Generator
  513. {
  514. yield [[new Line('* @param int $foo')], '$foo'];
  515. yield [[new Line('* @param int $foo some description')], '$foo'];
  516. yield [[new Line('/** @param int $foo*/')], '$foo'];
  517. yield [[new Line('* @param int')], null];
  518. yield [[new Line('* @var int $foo')], '$foo'];
  519. yield [[new Line('* @var int $foo some description')], '$foo'];
  520. yield [[new Line('/** @var int $foo*/')], '$foo'];
  521. yield [[new Line('* @var int')], null];
  522. }
  523. }