DocLexerTest.php 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321
  1. <?php
  2. namespace EasySwoole\DoctrineAnnotation\Tests;
  3. use EasySwoole\DoctrineAnnotation\DocLexer;
  4. use PHPUnit\Framework\TestCase;
  5. use function str_repeat;
  6. class DocLexerTest extends TestCase
  7. {
  8. public function testMarkerAnnotation(): void
  9. {
  10. $lexer = new DocLexer();
  11. $lexer->setInput('@Name');
  12. self::assertNull($lexer->token);
  13. self::assertNull($lexer->lookahead);
  14. self::assertTrue($lexer->moveNext());
  15. self::assertNull($lexer->token);
  16. self::assertEquals('@', $lexer->lookahead['value']);
  17. self::assertTrue($lexer->moveNext());
  18. self::assertEquals('@', $lexer->token['value']);
  19. self::assertEquals('Name', $lexer->lookahead['value']);
  20. self::assertFalse($lexer->moveNext());
  21. }
  22. public function testScannerTokenizesDocBlockWhitConstants(): void
  23. {
  24. $lexer = new DocLexer();
  25. $docblock = '@AnnotationWithConstants(
  26. PHP_EOL,
  27. ClassWithConstants::SOME_VALUE,
  28. ClassWithConstants::CONSTANT_,
  29. ClassWithConstants::CONST_ANT3,
  30. \EasySwoole\DoctrineAnnotation\Tests\Fixtures\InterfaceWithConstants::SOME_VALUE
  31. )';
  32. $tokens = [
  33. [
  34. 'value' => '@',
  35. 'position' => 0,
  36. 'type' => DocLexer::T_AT,
  37. ],
  38. [
  39. 'value' => 'AnnotationWithConstants',
  40. 'position' => 1,
  41. 'type' => DocLexer::T_IDENTIFIER,
  42. ],
  43. [
  44. 'value' => '(',
  45. 'position' => 24,
  46. 'type' => DocLexer::T_OPEN_PARENTHESIS,
  47. ],
  48. [
  49. 'value' => 'PHP_EOL',
  50. 'position' => 38,
  51. 'type' => DocLexer::T_IDENTIFIER,
  52. ],
  53. [
  54. 'value' => ',',
  55. 'position' => 45,
  56. 'type' => DocLexer::T_COMMA,
  57. ],
  58. [
  59. 'value' => 'ClassWithConstants::SOME_VALUE',
  60. 'position' => 59,
  61. 'type' => DocLexer::T_IDENTIFIER,
  62. ],
  63. [
  64. 'value' => ',',
  65. 'position' => 89,
  66. 'type' => DocLexer::T_COMMA,
  67. ],
  68. [
  69. 'value' => 'ClassWithConstants::CONSTANT_',
  70. 'position' => 103,
  71. 'type' => DocLexer::T_IDENTIFIER,
  72. ],
  73. [
  74. 'value' => ',',
  75. 'position' => 132,
  76. 'type' => DocLexer::T_COMMA,
  77. ],
  78. [
  79. 'value' => 'ClassWithConstants::CONST_ANT3',
  80. 'position' => 146,
  81. 'type' => DocLexer::T_IDENTIFIER,
  82. ],
  83. [
  84. 'value' => ',',
  85. 'position' => 176,
  86. 'type' => DocLexer::T_COMMA,
  87. ],
  88. [
  89. 'value' => '\EasySwoole\DoctrineAnnotation\Tests\Fixtures\InterfaceWithConstants::SOME_VALUE',
  90. 'position' => 190,
  91. 'type' => DocLexer::T_IDENTIFIER,
  92. ],
  93. [
  94. 'value' => ')',
  95. 'position' => 279,
  96. 'type' => DocLexer::T_CLOSE_PARENTHESIS,
  97. ],
  98. ];
  99. $lexer->setInput($docblock);
  100. foreach ($tokens as $expected) {
  101. $lexer->moveNext();
  102. $lookahead = $lexer->lookahead;
  103. self::assertEquals($expected['value'], $lookahead['value']);
  104. self::assertEquals($expected['type'], $lookahead['type']);
  105. self::assertEquals($expected['position'], $lookahead['position']);
  106. }
  107. self::assertFalse($lexer->moveNext());
  108. }
  109. public function testScannerTokenizesDocBlockWhitInvalidIdentifier(): void
  110. {
  111. $lexer = new DocLexer();
  112. $docblock = '@Foo\3.42';
  113. $tokens = [
  114. [
  115. 'value' => '@',
  116. 'position' => 0,
  117. 'type' => DocLexer::T_AT,
  118. ],
  119. [
  120. 'value' => 'Foo',
  121. 'position' => 1,
  122. 'type' => DocLexer::T_IDENTIFIER,
  123. ],
  124. [
  125. 'value' => '\\',
  126. 'position' => 4,
  127. 'type' => DocLexer::T_NAMESPACE_SEPARATOR,
  128. ],
  129. [
  130. 'value' => 3.42,
  131. 'position' => 5,
  132. 'type' => DocLexer::T_FLOAT,
  133. ],
  134. ];
  135. $lexer->setInput($docblock);
  136. foreach ($tokens as $expected) {
  137. $lexer->moveNext();
  138. $lookahead = $lexer->lookahead;
  139. self::assertEquals($expected['value'], $lookahead['value']);
  140. self::assertEquals($expected['type'], $lookahead['type']);
  141. self::assertEquals($expected['position'], $lookahead['position']);
  142. }
  143. self::assertFalse($lexer->moveNext());
  144. }
  145. /**
  146. * @group 44
  147. */
  148. public function testWithinDoubleQuotesVeryVeryLongStringWillNotOverflowPregSplitStackLimit(): void
  149. {
  150. $lexer = new DocLexer();
  151. $lexer->setInput('"' . str_repeat('.', 20240) . '"');
  152. self::assertIsArray($lexer->glimpse());
  153. }
  154. /**
  155. * @group 44
  156. */
  157. public function testRecognizesDoubleQuotesEscapeSequence(): void
  158. {
  159. $lexer = new DocLexer();
  160. $docblock = '@Foo("""' . "\n" . '""")';
  161. $tokens = [
  162. [
  163. 'value' => '@',
  164. 'position' => 0,
  165. 'type' => DocLexer::T_AT,
  166. ],
  167. [
  168. 'value' => 'Foo',
  169. 'position' => 1,
  170. 'type' => DocLexer::T_IDENTIFIER,
  171. ],
  172. [
  173. 'value' => '(',
  174. 'position' => 4,
  175. 'type' => DocLexer::T_OPEN_PARENTHESIS,
  176. ],
  177. [
  178. 'value' => "\"\n\"",
  179. 'position' => 5,
  180. 'type' => DocLexer::T_STRING,
  181. ],
  182. [
  183. 'value' => ')',
  184. 'position' => 12,
  185. 'type' => DocLexer::T_CLOSE_PARENTHESIS,
  186. ],
  187. ];
  188. $lexer->setInput($docblock);
  189. foreach ($tokens as $expected) {
  190. $lexer->moveNext();
  191. $lookahead = $lexer->lookahead;
  192. self::assertEquals($expected['value'], $lookahead['value']);
  193. self::assertEquals($expected['type'], $lookahead['type']);
  194. self::assertEquals($expected['position'], $lookahead['position']);
  195. }
  196. self::assertFalse($lexer->moveNext());
  197. }
  198. public function testDoesNotRecognizeFullAnnotationWithDashInIt(): void
  199. {
  200. $this->expectDocblockTokens(
  201. '@foo-bar--',
  202. [
  203. [
  204. 'value' => '@',
  205. 'position' => 0,
  206. 'type' => DocLexer::T_AT,
  207. ],
  208. [
  209. 'value' => 'foo',
  210. 'position' => 1,
  211. 'type' => DocLexer::T_IDENTIFIER,
  212. ],
  213. [
  214. 'value' => '-',
  215. 'position' => 4,
  216. 'type' => DocLexer::T_MINUS,
  217. ],
  218. [
  219. 'value' => 'bar',
  220. 'position' => 5,
  221. 'type' => DocLexer::T_IDENTIFIER,
  222. ],
  223. [
  224. 'value' => '-',
  225. 'position' => 8,
  226. 'type' => DocLexer::T_MINUS,
  227. ],
  228. [
  229. 'value' => '-',
  230. 'position' => 9,
  231. 'type' => DocLexer::T_MINUS,
  232. ],
  233. ]
  234. );
  235. }
  236. public function testRecognizesNegativeNumbers(): void
  237. {
  238. $this->expectDocblockTokens(
  239. '-12.34 -56',
  240. [
  241. [
  242. 'value' => '-12.34',
  243. 'position' => 0,
  244. 'type' => DocLexer::T_FLOAT,
  245. ],
  246. [
  247. 'value' => '-56',
  248. 'position' => 7,
  249. 'type' => DocLexer::T_INTEGER,
  250. ],
  251. ]
  252. );
  253. }
  254. /**
  255. * @phpstan-param list<array{value: mixed, position: int, type:int}> $expectedTokens
  256. */
  257. private function expectDocblockTokens(string $docBlock, array $expectedTokens): void
  258. {
  259. $lexer = new DocLexer();
  260. $lexer->setInput($docBlock);
  261. $actualTokens = [];
  262. while ($lexer->moveNext()) {
  263. $lookahead = $lexer->lookahead;
  264. $actualTokens[] = [
  265. 'value' => $lookahead['value'],
  266. 'type' => $lookahead['type'],
  267. 'position' => $lookahead['position'],
  268. ];
  269. }
  270. self::assertEquals($expectedTokens, $actualTokens);
  271. }
  272. public function testTokenAdjacency(): void
  273. {
  274. $lexer = new DocLexer();
  275. $lexer->setInput('-- -');
  276. self::assertTrue($lexer->nextTokenIsAdjacent());
  277. self::assertTrue($lexer->moveNext());
  278. self::assertTrue($lexer->nextTokenIsAdjacent());
  279. self::assertTrue($lexer->moveNext());
  280. self::assertTrue($lexer->nextTokenIsAdjacent());
  281. self::assertTrue($lexer->moveNext());
  282. self::assertFalse($lexer->nextTokenIsAdjacent());
  283. self::assertFalse($lexer->moveNext());
  284. }
  285. }