ProjectCodeTest.php 28 KB

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