CodeFormatterTest.php 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276
  1. <?php
  2. /*
  3. * This file is part of Psy Shell.
  4. *
  5. * (c) 2012-2023 Justin Hileman
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. namespace Psy\Test\Formatter;
  11. use Psy\Configuration;
  12. use Psy\Formatter\CodeFormatter;
  13. use Psy\Test\Formatter\Fixtures\SomeClass;
  14. class CodeFormatterTest extends \Psy\Test\TestCase
  15. {
  16. /**
  17. * @dataProvider reflectors
  18. */
  19. public function testFormat($reflector, $expected)
  20. {
  21. $formatted = CodeFormatter::format($reflector, Configuration::COLOR_MODE_FORCED);
  22. $formattedWithoutColors = self::stripTags($formatted);
  23. $this->assertSame($expected, self::trimLines($formattedWithoutColors));
  24. $this->assertNotEquals($expected, self::trimLines($formatted));
  25. }
  26. public function reflectors()
  27. {
  28. $expectClass = <<<'EOS'
  29. 14: class SomeClass
  30. 15: {
  31. 16: const SOME_CONST = 'some const';
  32. 17: private $someProp = 'some prop';
  33. 18:
  34. 19: public function someMethod($someParam)
  35. 20: {
  36. 21: return 'some method';
  37. 22: }
  38. 23:
  39. 24: public static function someClosure()
  40. 25: {
  41. 26: return function () {
  42. 27: return 'some closure';
  43. 28: };
  44. 29: }
  45. 30: }
  46. EOS;
  47. $expectMethod = <<<'EOS'
  48. 19: public function someMethod($someParam)
  49. 20: {
  50. 21: return 'some method';
  51. 22: }
  52. EOS;
  53. $expectClosure = <<<'EOS'
  54. 26: return function () {
  55. 27: return 'some closure';
  56. 28: };
  57. EOS;
  58. return [
  59. [new \ReflectionClass(SomeClass::class), $expectClass],
  60. [new \ReflectionObject(new SomeClass()), $expectClass],
  61. [new \ReflectionMethod(SomeClass::class, 'someMethod'), $expectMethod],
  62. [new \ReflectionFunction(SomeClass::someClosure()), $expectClosure],
  63. ];
  64. }
  65. /**
  66. * @dataProvider invalidReflectors
  67. */
  68. public function testCodeFormatterThrowsExceptionForReflectorsItDoesntUnderstand($reflector)
  69. {
  70. $this->expectException(\Psy\Exception\RuntimeException::class);
  71. CodeFormatter::format($reflector);
  72. $this->fail();
  73. }
  74. public function invalidReflectors()
  75. {
  76. $reflectors = [
  77. [new \ReflectionExtension('json')],
  78. [new \ReflectionParameter([SomeClass::class, 'someMethod'], 'someParam')],
  79. [new \ReflectionProperty(SomeClass::class, 'someProp')],
  80. ];
  81. if (\version_compare(\PHP_VERSION, '7.1.0', '>=')) {
  82. $reflectors[] = [new \ReflectionClassConstant(SomeClass::class, 'SOME_CONST')];
  83. }
  84. return $reflectors;
  85. }
  86. /**
  87. * @dataProvider filenames
  88. */
  89. public function testCodeFormatterThrowsExceptionForMissingFile($filename)
  90. {
  91. $this->expectException(\Psy\Exception\RuntimeException::class);
  92. $reflector = $this->getMockBuilder(\ReflectionClass::class)
  93. ->disableOriginalConstructor()
  94. ->getMock();
  95. $reflector
  96. ->expects($this->once())
  97. ->method('getFileName')
  98. ->willReturn($filename);
  99. CodeFormatter::format($reflector);
  100. $this->fail();
  101. }
  102. public function filenames()
  103. {
  104. return [[false], ['not a file']];
  105. }
  106. /**
  107. * @dataProvider validCode
  108. */
  109. public function testFormatCode($code, $startLine, $endLine, $markLine, $expected)
  110. {
  111. $formatted = CodeFormatter::formatCode($code, $startLine, $endLine, $markLine);
  112. $formattedWithoutColors = self::stripTags($formatted);
  113. $this->assertSame($expected, self::trimLines($formattedWithoutColors));
  114. $this->assertNotEquals($expected, self::trimLines($formatted));
  115. }
  116. public function validCode()
  117. {
  118. $someCode = <<<'EOS'
  119. <?php
  120. /*
  121. * This file is part of Psy Shell.
  122. *
  123. * (c) 2012-2023 Justin Hileman
  124. *
  125. * For the full copyright and license information, please view the LICENSE
  126. * file that was distributed with this source code.
  127. */
  128. namespace Psy\Test\Formatter\Fixtures;
  129. class SomeClass
  130. {
  131. const SOME_CONST = 'some const';
  132. private $someProp = 'some prop';
  133. public function someMethod($someParam)
  134. {
  135. return 'some method';
  136. }
  137. public static function someClosure()
  138. {
  139. return function () {
  140. return 'some closure';
  141. };
  142. }
  143. }
  144. EOS;
  145. $someCodeExpected = <<<'EOS'
  146. 1: <?php
  147. 2:
  148. 3: /*
  149. 4: * This file is part of Psy Shell.
  150. 5: *
  151. 6: * (c) 2012-2023 Justin Hileman
  152. 7: *
  153. 8: * For the full copyright and license information, please view the LICENSE
  154. 9: * file that was distributed with this source code.
  155. 10: */
  156. 11:
  157. 12: namespace Psy\Test\Formatter\Fixtures;
  158. 13:
  159. 14: class SomeClass
  160. 15: {
  161. 16: const SOME_CONST = 'some const';
  162. 17: private $someProp = 'some prop';
  163. 18:
  164. 19: public function someMethod($someParam)
  165. 20: {
  166. 21: return 'some method';
  167. 22: }
  168. 23:
  169. 24: public static function someClosure()
  170. 25: {
  171. 26: return function () {
  172. 27: return 'some closure';
  173. 28: };
  174. 29: }
  175. 30: }
  176. EOS;
  177. $someCodeSnippet = <<<'EOS'
  178. 19: public function someMethod($someParam)
  179. 20: {
  180. 21: return 'some method';
  181. 22: }
  182. EOS;
  183. $someCodeSnippetWithMarker = <<<'EOS'
  184. 19: public function someMethod($someParam)
  185. > 20: {
  186. 21: return 'some method';
  187. 22: }
  188. EOS;
  189. return [
  190. [$someCode, 1, null, null, $someCodeExpected],
  191. [$someCode, 19, 22, null, $someCodeSnippet],
  192. [$someCode, 19, 22, 20, $someCodeSnippetWithMarker],
  193. ];
  194. }
  195. /**
  196. * Test some smaller ones with spans... we don't want the test to be tooo flaky so we don't
  197. * explicitly test the exact formatting above. Just to be safe, let's add a couple of tests
  198. * that *do* expect specific formatting.
  199. *
  200. * @dataProvider smallCodeLines
  201. */
  202. public function testFormatSmallCodeLines($code, $startLine, $endLine, $markLine, $expected)
  203. {
  204. $formatted = CodeFormatter::formatCode($code, $startLine, $endLine, $markLine);
  205. $this->assertSame($expected, self::trimLines($formatted));
  206. }
  207. public function smallCodeLines()
  208. {
  209. return [
  210. ['<?php $foo = 42;', 1, null, null, '<aside>1</aside>: \\<?php $foo <keyword>= </keyword><number>42</number><keyword>;</keyword>'],
  211. ['<?php echo "yay $foo!";', 1, null, null, '<aside>1</aside>: \\<?php <keyword>echo </keyword><string>"yay </string>$foo<string>!"</string><keyword>;</keyword>'],
  212. // Start and end lines
  213. ["<?php echo 'wat';\n\$foo = 42;", 1, 1, null, '<aside>1</aside>: \\<?php <keyword>echo </keyword><string>\'wat\'</string><keyword>;</keyword>'],
  214. ["<?php echo 'wat';\n\$foo = 42;", 2, 2, null, '<aside>2</aside>: $foo <keyword>= </keyword><number>42</number><keyword>;</keyword>'],
  215. ["<?php echo 'wat';\n\$foo = 42;", 2, null, null, '<aside>2</aside>: $foo <keyword>= </keyword><number>42</number><keyword>;</keyword>'],
  216. // With a line marker
  217. ["<?php echo 'wat';\n\$foo = 42;", 2, null, 2, ' <urgent>></urgent> <aside>2</aside>: $foo <keyword>= </keyword><number>42</number><keyword>;</keyword>'],
  218. // Line marker before or after our line range
  219. ["<?php echo 'wat';\n\$foo = 42;", 2, null, 1, '<aside>2</aside>: $foo <keyword>= </keyword><number>42</number><keyword>;</keyword>'],
  220. ["<?php echo 'wat';\n\$foo = 42;", 1, 1, 3, '<aside>1</aside>: \<?php <keyword>echo </keyword><string>\'wat\'</string><keyword>;</keyword>'],
  221. ];
  222. }
  223. /**
  224. * Remove tags from formatted output. This is kind of ugly o_O.
  225. */
  226. private static function stripTags($code)
  227. {
  228. $tagRegex = '[a-z][^<>]*+';
  229. $output = \preg_replace("#<(($tagRegex) | /($tagRegex)?)>#ix", '', $code);
  230. return \str_replace('\\<', '<', $output);
  231. }
  232. private static function trimLines($code)
  233. {
  234. return \rtrim(\implode("\n", \array_map('rtrim', \explode("\n", $code))));
  235. }
  236. }