ProjectCodeTest.php 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803
  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\AutoReview;
  13. use PhpCsFixer\AbstractProxyFixer;
  14. use PhpCsFixer\DocBlock\Annotation;
  15. use PhpCsFixer\DocBlock\DocBlock;
  16. use PhpCsFixer\Fixer\AbstractPhpUnitFixer;
  17. use PhpCsFixer\Fixer\PhpUnit\PhpUnitNamespacedFixer;
  18. use PhpCsFixer\FixerFactory;
  19. use PhpCsFixer\Preg;
  20. use PhpCsFixer\Tests\Test\AbstractFixerTestCase;
  21. use PhpCsFixer\Tests\Test\AbstractIntegrationTestCase;
  22. use PhpCsFixer\Tests\TestCase;
  23. use PhpCsFixer\Tokenizer\Token;
  24. use PhpCsFixer\Tokenizer\Tokens;
  25. use Symfony\Component\Finder\Finder;
  26. use Symfony\Component\Finder\SplFileInfo;
  27. /**
  28. * @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
  29. *
  30. * @internal
  31. *
  32. * @coversNothing
  33. *
  34. * @group auto-review
  35. * @group covers-nothing
  36. */
  37. final class ProjectCodeTest extends TestCase
  38. {
  39. /**
  40. * @var null|list<array{class-string<TestCase>}>
  41. */
  42. private static ?array $testClassCases = null;
  43. /**
  44. * This structure contains older classes that are not yet covered by tests.
  45. *
  46. * It may only shrink, never add anything to it.
  47. *
  48. * @var string[]
  49. */
  50. private static $classesWithoutTests = [
  51. \PhpCsFixer\Console\Command\DocumentationCommand::class,
  52. \PhpCsFixer\Console\SelfUpdate\GithubClient::class,
  53. \PhpCsFixer\Doctrine\Annotation\DocLexer::class,
  54. \PhpCsFixer\Documentation\DocumentationLocator::class,
  55. \PhpCsFixer\Documentation\FixerDocumentGenerator::class,
  56. \PhpCsFixer\Documentation\ListDocumentGenerator::class,
  57. \PhpCsFixer\Documentation\RstUtils::class,
  58. \PhpCsFixer\Documentation\RuleSetDocumentationGenerator::class,
  59. \PhpCsFixer\Runner\FileCachingLintingIterator::class,
  60. ];
  61. public static function tearDownAfterClass(): void
  62. {
  63. self::$testClassCases = null;
  64. }
  65. public function testThatClassesWithoutTestsVarIsProper(): void
  66. {
  67. $unknownClasses = array_filter(
  68. self::$classesWithoutTests,
  69. static fn (string $class): bool => !class_exists($class) && !trait_exists($class),
  70. );
  71. self::assertSame([], $unknownClasses);
  72. }
  73. /**
  74. * @dataProvider provideThatSrcClassHaveTestClassCases
  75. */
  76. public function testThatSrcClassHaveTestClass(string $className): void
  77. {
  78. $testClassName = 'PhpCsFixer\\Tests'.substr($className, 10).'Test';
  79. if (\in_array($className, self::$classesWithoutTests, true)) {
  80. self::assertFalse(class_exists($testClassName), sprintf('Class "%s" already has tests, so it should be removed from "%s::$classesWithoutTests".', $className, __CLASS__));
  81. self::markTestIncomplete(sprintf('Class "%s" has no tests yet, please help and add it.', $className));
  82. }
  83. self::assertTrue(class_exists($testClassName), sprintf('Expected test class "%s" for "%s" not found.', $testClassName, $className));
  84. self::assertTrue(is_subclass_of($testClassName, TestCase::class), sprintf('Expected test class "%s" to be a subclass of "\PhpCsFixer\Tests\TestCase".', $testClassName));
  85. }
  86. /**
  87. * @dataProvider provideThatSrcClassesNotAbuseInterfacesCases
  88. */
  89. public function testThatSrcClassesNotAbuseInterfaces(string $className): void
  90. {
  91. $rc = new \ReflectionClass($className);
  92. $allowedMethods = array_map(
  93. fn (\ReflectionClass $interface): array => $this->getPublicMethodNames($interface),
  94. $rc->getInterfaces()
  95. );
  96. if (\count($allowedMethods) > 0) {
  97. $allowedMethods = array_unique(array_merge(...array_values($allowedMethods)));
  98. }
  99. $allowedMethods[] = '__construct';
  100. $allowedMethods[] = '__destruct';
  101. $allowedMethods[] = '__wakeup';
  102. $exceptionMethods = [
  103. 'configure', // due to AbstractFixer::configure
  104. 'getConfigurationDefinition', // due to AbstractFixer::getConfigurationDefinition
  105. 'getDefaultConfiguration', // due to AbstractFixer::getDefaultConfiguration
  106. 'setWhitespacesConfig', // due to AbstractFixer::setWhitespacesConfig
  107. ];
  108. $definedMethods = $this->getPublicMethodNames($rc);
  109. $extraMethods = array_diff(
  110. $definedMethods,
  111. $allowedMethods,
  112. $exceptionMethods
  113. );
  114. sort($extraMethods);
  115. self::assertEmpty(
  116. $extraMethods,
  117. sprintf(
  118. "Class '%s' should not have public methods that are not part of implemented interfaces.\nViolations:\n%s",
  119. $className,
  120. implode("\n", array_map(static fn (string $item): string => " * {$item}", $extraMethods))
  121. )
  122. );
  123. }
  124. /**
  125. * @dataProvider provideSrcClassCases
  126. */
  127. public function testThatSrcClassesNotExposeProperties(string $className): void
  128. {
  129. $rc = new \ReflectionClass($className);
  130. self::assertEmpty(
  131. $rc->getProperties(\ReflectionProperty::IS_PUBLIC),
  132. sprintf('Class \'%s\' should not have public properties.', $className)
  133. );
  134. if ($rc->isFinal()) {
  135. return;
  136. }
  137. $allowedProps = [];
  138. $definedProps = $rc->getProperties(\ReflectionProperty::IS_PROTECTED);
  139. if (false !== $rc->getParentClass()) {
  140. $allowedProps = $rc->getParentClass()->getProperties(\ReflectionProperty::IS_PROTECTED);
  141. }
  142. $allowedProps = array_map(static fn (\ReflectionProperty $item): string => $item->getName(), $allowedProps);
  143. $definedProps = array_map(static fn (\ReflectionProperty $item): string => $item->getName(), $definedProps);
  144. $exceptionPropsPerClass = [
  145. \PhpCsFixer\AbstractPhpdocTypesFixer::class => ['tags'],
  146. \PhpCsFixer\AbstractFixer::class => ['configuration', 'configurationDefinition', 'whitespacesConfig'],
  147. AbstractProxyFixer::class => ['proxyFixers'],
  148. ];
  149. $extraProps = array_diff(
  150. $definedProps,
  151. $allowedProps,
  152. $exceptionPropsPerClass[$className] ?? []
  153. );
  154. sort($extraProps);
  155. self::assertEmpty(
  156. $extraProps,
  157. sprintf(
  158. "Class '%s' should not have protected properties.\nViolations:\n%s",
  159. $className,
  160. implode("\n", array_map(static fn (string $item): string => " * {$item}", $extraProps))
  161. )
  162. );
  163. }
  164. /**
  165. * @dataProvider provideTestClassCases
  166. */
  167. public function testThatTestClassesAreTraitOrAbstractOrFinal(string $testClassName): void
  168. {
  169. $rc = new \ReflectionClass($testClassName);
  170. self::assertTrue(
  171. $rc->isTrait() || $rc->isAbstract() || $rc->isFinal(),
  172. sprintf('Test class %s should be trait, abstract or final.', $testClassName)
  173. );
  174. }
  175. /**
  176. * @dataProvider provideTestClassCases
  177. */
  178. public function testThatTestClassesAreInternal(string $testClassName): void
  179. {
  180. $rc = new \ReflectionClass($testClassName);
  181. $doc = new DocBlock($rc->getDocComment());
  182. self::assertNotEmpty(
  183. $doc->getAnnotationsOfType('internal'),
  184. sprintf('Test class %s should have internal annotation.', $testClassName)
  185. );
  186. }
  187. /**
  188. * @dataProvider provideTestClassCases
  189. */
  190. public function testThatTestClassesPublicMethodsAreCorrectlyNamed(string $testClassName): void
  191. {
  192. $reflectionClass = new \ReflectionClass($testClassName);
  193. $publicMethods = array_filter(
  194. $reflectionClass->getMethods(\ReflectionMethod::IS_PUBLIC),
  195. static fn (\ReflectionMethod $reflectionMethod): bool => $reflectionMethod->getDeclaringClass()->getName() === $reflectionClass->getName()
  196. );
  197. if ([] === $publicMethods) {
  198. $this->expectNotToPerformAssertions(); // no methods to test, all good!
  199. return;
  200. }
  201. foreach ($publicMethods as $method) {
  202. self::assertMatchesRegularExpression(
  203. '/^(test|expect|provide|setUpBeforeClass$|tearDownAfterClass$|__construct$)/',
  204. $method->getName(),
  205. sprintf('Public method "%s::%s" is not properly named.', $reflectionClass->getName(), $method->getName())
  206. );
  207. }
  208. }
  209. /**
  210. * @dataProvider provideDataProviderMethodCases
  211. */
  212. public function testThatTestDataProvidersAreUsed(string $testClassName, \ReflectionMethod $dataProvider): void
  213. {
  214. $usedDataProviderMethodNames = [];
  215. foreach ($this->getUsedDataProviderMethodNames($testClassName) as $providerName) {
  216. $usedDataProviderMethodNames[] = $providerName;
  217. }
  218. $dataProviderName = $dataProvider->getName();
  219. self::assertContains(
  220. $dataProviderName,
  221. $usedDataProviderMethodNames,
  222. sprintf('Data provider in "%s" with name "%s" is not used.', $dataProvider->getDeclaringClass()->getName(), $dataProviderName)
  223. );
  224. }
  225. /**
  226. * @dataProvider provideDataProviderMethodCases
  227. */
  228. public function testThatTestDataProvidersAreCorrectlyNamed(string $testClassName, \ReflectionMethod $dataProvider): void
  229. {
  230. $dataProviderName = $dataProvider->getShortName();
  231. self::assertMatchesRegularExpression('/^provide[A-Z]\S+Cases$/', $dataProviderName, sprintf(
  232. 'Data provider in "%s" with name "%s" is not correctly named.',
  233. $testClassName,
  234. $dataProviderName
  235. ));
  236. }
  237. public static function provideDataProviderMethodCases(): iterable
  238. {
  239. foreach (self::provideTestClassCases() as $testClassName) {
  240. $testClassName = reset($testClassName);
  241. $reflectionClass = new \ReflectionClass($testClassName);
  242. $methods = array_filter(
  243. $reflectionClass->getMethods(\ReflectionMethod::IS_PUBLIC),
  244. static fn (\ReflectionMethod $reflectionMethod): bool => $reflectionMethod->getDeclaringClass()->getName() === $reflectionClass->getName() && str_starts_with($reflectionMethod->getName(), 'provide')
  245. );
  246. foreach ($methods as $method) {
  247. yield [$testClassName, $method];
  248. }
  249. }
  250. }
  251. /**
  252. * @dataProvider provideTestClassCases
  253. */
  254. public function testThatTestClassCoversAreCorrect(string $testClassName): void
  255. {
  256. $reflectionClass = new \ReflectionClass($testClassName);
  257. if ($reflectionClass->isAbstract() || $reflectionClass->isInterface()) {
  258. $this->expectNotToPerformAssertions();
  259. return;
  260. }
  261. $doc = $reflectionClass->getDocComment();
  262. self::assertNotFalse($doc);
  263. if (Preg::match('/@coversNothing/', $doc, $matches)) {
  264. return;
  265. }
  266. $covers = Preg::matchAll('/@covers (\S*)/', $doc, $matches);
  267. self::assertGreaterThanOrEqual(1, $covers, sprintf('Missing @covers in PHPDoc of test class "%s".', $testClassName));
  268. array_shift($matches);
  269. $class = '\\'.str_replace('PhpCsFixer\Tests\\', 'PhpCsFixer\\', substr($testClassName, 0, -4));
  270. $parentClass = (new \ReflectionClass($class))->getParentClass();
  271. $parentClassName = false === $parentClass ? null : '\\'.$parentClass->getName();
  272. foreach ($matches as $match) {
  273. $classMatch = array_shift($match);
  274. self::assertTrue(
  275. $classMatch === $class || $parentClassName === $classMatch,
  276. sprintf('Unexpected @covers "%s" for "%s".', $classMatch, $testClassName)
  277. );
  278. }
  279. }
  280. /**
  281. * @dataProvider provideThereIsNoPregFunctionUsedDirectlyCases
  282. */
  283. public function testThereIsNoPregFunctionUsedDirectly(string $className): void
  284. {
  285. $rc = new \ReflectionClass($className);
  286. $tokens = Tokens::fromCode(file_get_contents($rc->getFileName()));
  287. $stringTokens = array_filter(
  288. $tokens->toArray(),
  289. static fn (Token $token): bool => $token->isGivenKind(T_STRING)
  290. );
  291. $strings = array_map(
  292. static fn (Token $token): string => $token->getContent(),
  293. $stringTokens
  294. );
  295. $strings = array_unique($strings);
  296. $message = sprintf('Class %s must not use preg_*, it shall use Preg::* instead.', $className);
  297. self::assertNotContains('preg_filter', $strings, $message);
  298. self::assertNotContains('preg_grep', $strings, $message);
  299. self::assertNotContains('preg_match', $strings, $message);
  300. self::assertNotContains('preg_match_all', $strings, $message);
  301. self::assertNotContains('preg_replace', $strings, $message);
  302. self::assertNotContains('preg_replace_callback', $strings, $message);
  303. self::assertNotContains('preg_split', $strings, $message);
  304. }
  305. /**
  306. * @dataProvider provideTestClassCases
  307. */
  308. public function testExpectedInputOrder(string $testClassName): void
  309. {
  310. $reflectionClass = new \ReflectionClass($testClassName);
  311. $publicMethods = array_filter(
  312. $reflectionClass->getMethods(\ReflectionMethod::IS_PUBLIC),
  313. static fn (\ReflectionMethod $reflectionMethod): bool => $reflectionMethod->getDeclaringClass()->getName() === $reflectionClass->getName()
  314. );
  315. if ([] === $publicMethods) {
  316. $this->expectNotToPerformAssertions(); // no methods to test, all good!
  317. return;
  318. }
  319. /** @var \ReflectionMethod $method */
  320. foreach ($publicMethods as $method) {
  321. $parameters = $method->getParameters();
  322. if (\count($parameters) < 2) {
  323. $this->addToAssertionCount(1); // not enough parameters to test, all good!
  324. continue;
  325. }
  326. $expected = [
  327. 'expected' => false,
  328. 'input' => false,
  329. ];
  330. for ($i = \count($parameters) - 1; $i >= 0; --$i) {
  331. $name = $parameters[$i]->getName();
  332. if (isset($expected[$name])) {
  333. $expected[$name] = $i;
  334. }
  335. }
  336. $expected = array_filter($expected, static fn ($item): bool => false !== $item);
  337. if (\count($expected) < 2) {
  338. $this->addToAssertionCount(1); // not enough parameters to test, all good!
  339. continue;
  340. }
  341. self::assertLessThan(
  342. $expected['input'],
  343. $expected['expected'],
  344. sprintf('Public method "%s::%s" has parameter \'input\' before \'expected\'.', $reflectionClass->getName(), $method->getName())
  345. );
  346. }
  347. }
  348. /**
  349. * @dataProvider provideSrcClassCases
  350. * @dataProvider provideTestClassCases
  351. */
  352. public function testAllCodeContainSingleClassy(string $className): void
  353. {
  354. $headerTypes = [
  355. T_ABSTRACT,
  356. T_AS,
  357. T_COMMENT,
  358. T_DECLARE,
  359. T_DOC_COMMENT,
  360. T_FINAL,
  361. T_LNUMBER,
  362. T_NAMESPACE,
  363. T_NS_SEPARATOR,
  364. T_OPEN_TAG,
  365. T_STRING,
  366. T_USE,
  367. T_WHITESPACE,
  368. ];
  369. $rc = new \ReflectionClass($className);
  370. $file = $rc->getFileName();
  371. $tokens = Tokens::fromCode(file_get_contents($file));
  372. $classyIndex = null;
  373. self::assertTrue($tokens->isAnyTokenKindsFound(Token::getClassyTokenKinds()), sprintf('File "%s" should contains a classy.', $file));
  374. $count = \count($tokens);
  375. for ($index = 1; $index < $count; ++$index) {
  376. if ($tokens[$index]->isClassy()) {
  377. $classyIndex = $index;
  378. break;
  379. }
  380. if (\defined('T_ATTRIBUTE') && $tokens[$index]->isGivenKind(T_ATTRIBUTE)) {
  381. $index = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_ATTRIBUTE, $index);
  382. continue;
  383. }
  384. if (!$tokens[$index]->isGivenKind($headerTypes) && !$tokens[$index]->equalsAny([';', '=', '(', ')'])) {
  385. self::fail(sprintf('File "%s" should only contains single classy, found "%s" @ %d.', $file, $tokens[$index]->toJson(), $index));
  386. }
  387. }
  388. self::assertNotNull($classyIndex, sprintf('File "%s" does not contain a classy.', $file));
  389. $nextTokenOfKind = $tokens->getNextTokenOfKind($classyIndex, ['{']);
  390. if (!\is_int($nextTokenOfKind)) {
  391. throw new \UnexpectedValueException('Classy without {} - braces.');
  392. }
  393. $classyEndIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $nextTokenOfKind);
  394. self::assertNull($tokens->getNextNonWhitespace($classyEndIndex), sprintf('File "%s" should only contains a single classy.', $file));
  395. }
  396. /**
  397. * @dataProvider provideSrcClassCases
  398. */
  399. public function testInheritdocIsNotAbused(string $className): void
  400. {
  401. $rc = new \ReflectionClass($className);
  402. $allowedMethods = array_map(
  403. fn (\ReflectionClass $interface): array => $this->getPublicMethodNames($interface),
  404. $rc->getInterfaces()
  405. );
  406. if (\count($allowedMethods) > 0) {
  407. $allowedMethods = array_merge(...array_values($allowedMethods));
  408. }
  409. $parentClass = $rc;
  410. while (false !== $parentClass = $parentClass->getParentClass()) {
  411. foreach ($parentClass->getMethods(\ReflectionMethod::IS_PUBLIC | \ReflectionMethod::IS_PROTECTED) as $method) {
  412. $allowedMethods[] = $method->getName();
  413. }
  414. }
  415. $allowedMethods = array_unique($allowedMethods);
  416. $methodsWithInheritdoc = array_filter(
  417. $rc->getMethods(),
  418. static fn (\ReflectionMethod $rm): bool => false !== $rm->getDocComment() && stripos($rm->getDocComment(), '@inheritdoc')
  419. );
  420. $methodsWithInheritdoc = array_map(
  421. static fn (\ReflectionMethod $rm): string => $rm->getName(),
  422. $methodsWithInheritdoc
  423. );
  424. $extraMethods = array_diff($methodsWithInheritdoc, $allowedMethods);
  425. self::assertEmpty(
  426. $extraMethods,
  427. sprintf(
  428. "Class '%s' should not have methods with '@inheritdoc' in PHPDoc that are not inheriting PHPDoc.\nViolations:\n%s",
  429. $className,
  430. implode("\n", array_map(static fn ($item): string => " * {$item}", $extraMethods))
  431. )
  432. );
  433. }
  434. public static function provideSrcClassCases(): iterable
  435. {
  436. return array_map(
  437. static fn (string $item): array => [$item],
  438. self::getSrcClasses()
  439. );
  440. }
  441. public static function provideThatSrcClassesNotAbuseInterfacesCases(): iterable
  442. {
  443. return array_map(
  444. static fn (string $item): array => [$item],
  445. array_filter(self::getSrcClasses(), static function (string $className): bool {
  446. $rc = new \ReflectionClass($className);
  447. $doc = false !== $rc->getDocComment()
  448. ? new DocBlock($rc->getDocComment())
  449. : null;
  450. if (
  451. $rc->isInterface()
  452. || (null !== $doc && \count($doc->getAnnotationsOfType('internal')) > 0)
  453. || \in_array($className, [
  454. \PhpCsFixer\Finder::class,
  455. AbstractFixerTestCase::class,
  456. AbstractIntegrationTestCase::class,
  457. Tokens::class,
  458. ], true)
  459. ) {
  460. return false;
  461. }
  462. $interfaces = $rc->getInterfaces();
  463. $interfacesCount = \count($interfaces);
  464. if (0 === $interfacesCount) {
  465. return false;
  466. }
  467. if (1 === $interfacesCount) {
  468. $interface = reset($interfaces);
  469. if ('Stringable' === $interface->getName()) {
  470. return false;
  471. }
  472. }
  473. return true;
  474. })
  475. );
  476. }
  477. public static function provideThatSrcClassHaveTestClassCases(): iterable
  478. {
  479. return array_map(
  480. static fn (string $item): array => [$item],
  481. array_filter(
  482. self::getSrcClasses(),
  483. static function (string $className): bool {
  484. $rc = new \ReflectionClass($className);
  485. return !$rc->isTrait() && !$rc->isAbstract() && !$rc->isInterface() && \count($rc->getMethods()) > 0;
  486. }
  487. )
  488. );
  489. }
  490. /**
  491. * @return iterable<array{class-string<TestCase>}>
  492. */
  493. public static function provideTestClassCases(): iterable
  494. {
  495. if (null === self::$testClassCases) {
  496. self::$testClassCases = array_map(
  497. static fn (string $item): array => [$item],
  498. self::getTestClasses(),
  499. );
  500. }
  501. yield from self::$testClassCases;
  502. }
  503. public static function provideThereIsNoPregFunctionUsedDirectlyCases(): iterable
  504. {
  505. return array_map(
  506. static fn (string $item): array => [$item],
  507. array_filter(
  508. self::getSrcClasses(),
  509. static fn (string $className): bool => Preg::class !== $className,
  510. ),
  511. );
  512. }
  513. /**
  514. * @dataProvider providePhpUnitFixerExtendsAbstractPhpUnitFixerCases
  515. */
  516. public function testPhpUnitFixerExtendsAbstractPhpUnitFixer(string $className): void
  517. {
  518. $reflection = new \ReflectionClass($className);
  519. self::assertTrue($reflection->isSubclassOf(AbstractPhpUnitFixer::class));
  520. }
  521. public static function providePhpUnitFixerExtendsAbstractPhpUnitFixerCases(): iterable
  522. {
  523. $factory = new FixerFactory();
  524. $factory->registerBuiltInFixers();
  525. foreach ($factory->getFixers() as $fixer) {
  526. if (!str_starts_with($fixer->getName(), 'php_unit_')) {
  527. continue;
  528. }
  529. // this one fixes usage of PHPUnit classes
  530. if ($fixer instanceof PhpUnitNamespacedFixer) {
  531. continue;
  532. }
  533. if ($fixer instanceof AbstractProxyFixer) {
  534. continue;
  535. }
  536. yield [\get_class($fixer)];
  537. }
  538. }
  539. /**
  540. * @dataProvider provideSrcClassCases
  541. * @dataProvider provideTestClassCases
  542. */
  543. public function testConstantsAreInUpperCase(string $className): void
  544. {
  545. $rc = new \ReflectionClass($className);
  546. $reflectionClassConstants = $rc->getReflectionConstants();
  547. if (\count($reflectionClassConstants) < 1) {
  548. $this->expectNotToPerformAssertions();
  549. return;
  550. }
  551. foreach ($reflectionClassConstants as $constant) {
  552. $constantName = $constant->getName();
  553. self::assertSame(strtoupper($constantName), $constantName, $className);
  554. }
  555. }
  556. /**
  557. * @return iterable<string, string>
  558. */
  559. private function getUsedDataProviderMethodNames(string $testClassName): iterable
  560. {
  561. foreach ($this->getAnnotationsOfTestClass($testClassName, 'dataProvider') as $methodName => $dataProviderAnnotation) {
  562. if (1 === preg_match('/@dataProvider\s+(?P<methodName>\w+)/', $dataProviderAnnotation->getContent(), $matches)) {
  563. yield $methodName => $matches['methodName'];
  564. }
  565. }
  566. }
  567. /**
  568. * @return iterable<string, Annotation>
  569. */
  570. private function getAnnotationsOfTestClass(string $testClassName, string $annotation): iterable
  571. {
  572. $tokens = Tokens::fromCode(file_get_contents(
  573. str_replace('\\', \DIRECTORY_SEPARATOR, preg_replace('#^PhpCsFixer\\\Tests#', 'tests', $testClassName)).'.php'
  574. ));
  575. foreach ($tokens as $index => $token) {
  576. if (!$token->isGivenKind(T_DOC_COMMENT)) {
  577. continue;
  578. }
  579. $methodName = $tokens[$tokens->getNextTokenOfKind($index, [[T_STRING]])]->getContent();
  580. $docBlock = new DocBlock($token->getContent());
  581. $dataProviderAnnotations = $docBlock->getAnnotationsOfType($annotation);
  582. foreach ($dataProviderAnnotations as $dataProviderAnnotation) {
  583. yield $methodName => $dataProviderAnnotation;
  584. }
  585. }
  586. }
  587. /**
  588. * @return list<class-string>
  589. */
  590. private static function getSrcClasses(): array
  591. {
  592. static $classes;
  593. if (null !== $classes) {
  594. return $classes;
  595. }
  596. $finder = Finder::create()
  597. ->files()
  598. ->name('*.php')
  599. ->in(__DIR__.'/../../src')
  600. ->exclude([
  601. 'Resources',
  602. ])
  603. ;
  604. $classes = array_map(
  605. static fn (SplFileInfo $file): string => sprintf(
  606. '%s\\%s%s%s',
  607. 'PhpCsFixer',
  608. strtr($file->getRelativePath(), \DIRECTORY_SEPARATOR, '\\'),
  609. '' !== $file->getRelativePath() ? '\\' : '',
  610. $file->getBasename('.'.$file->getExtension())
  611. ),
  612. iterator_to_array($finder, false)
  613. );
  614. sort($classes);
  615. return $classes;
  616. }
  617. /**
  618. * @return list<class-string<TestCase>>
  619. */
  620. private static function getTestClasses(): array
  621. {
  622. static $classes;
  623. if (null !== $classes) {
  624. return $classes;
  625. }
  626. $finder = Finder::create()
  627. ->files()
  628. ->name('*.php')
  629. ->in(__DIR__.'/..')
  630. ->exclude([
  631. 'Fixtures',
  632. ])
  633. ;
  634. $classes = array_map(
  635. static fn (SplFileInfo $file): string => sprintf(
  636. 'PhpCsFixer\\Tests\\%s%s%s',
  637. strtr($file->getRelativePath(), \DIRECTORY_SEPARATOR, '\\'),
  638. '' !== $file->getRelativePath() ? '\\' : '',
  639. $file->getBasename('.'.$file->getExtension())
  640. ),
  641. iterator_to_array($finder, false)
  642. );
  643. $classes = array_filter($classes, static fn (string $class): bool => is_subclass_of($class, TestCase::class));
  644. sort($classes);
  645. return $classes;
  646. }
  647. /**
  648. * @param \ReflectionClass<object> $rc
  649. *
  650. * @return string[]
  651. */
  652. private function getPublicMethodNames(\ReflectionClass $rc): array
  653. {
  654. return array_map(
  655. static fn (\ReflectionMethod $rm): string => $rm->getName(),
  656. $rc->getMethods(\ReflectionMethod::IS_PUBLIC)
  657. );
  658. }
  659. }