ProjectCodeTest.php 44 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176
  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\AbstractFixer;
  14. use PhpCsFixer\AbstractPhpdocToTypeDeclarationFixer;
  15. use PhpCsFixer\AbstractPhpdocTypesFixer;
  16. use PhpCsFixer\AbstractProxyFixer;
  17. use PhpCsFixer\Console\Command\FixCommand;
  18. use PhpCsFixer\DocBlock\Annotation;
  19. use PhpCsFixer\DocBlock\DocBlock;
  20. use PhpCsFixer\Fixer\AbstractPhpUnitFixer;
  21. use PhpCsFixer\Fixer\ConfigurableFixerTrait;
  22. use PhpCsFixer\Fixer\PhpUnit\PhpUnitNamespacedFixer;
  23. use PhpCsFixer\FixerFactory;
  24. use PhpCsFixer\Preg;
  25. use PhpCsFixer\Tests\Test\AbstractFixerTestCase;
  26. use PhpCsFixer\Tests\Test\AbstractIntegrationTestCase;
  27. use PhpCsFixer\Tests\TestCase;
  28. use PhpCsFixer\Tokenizer\Token;
  29. use PhpCsFixer\Tokenizer\Tokens;
  30. use PhpCsFixer\Tokenizer\TokensAnalyzer;
  31. use Symfony\Component\Finder\Finder;
  32. use Symfony\Component\Finder\SplFileInfo;
  33. /**
  34. * @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
  35. *
  36. * @internal
  37. *
  38. * @coversNothing
  39. *
  40. * @group auto-review
  41. * @group covers-nothing
  42. */
  43. final class ProjectCodeTest extends TestCase
  44. {
  45. /**
  46. * @var null|array<string, array{class-string<TestCase>}>
  47. */
  48. private static ?array $testClassCases = null;
  49. /**
  50. * @var null|array<string, array{class-string}>
  51. */
  52. private static ?array $srcClassCases = null;
  53. /**
  54. * @var null|array<string, array{class-string, string}>
  55. */
  56. private static ?array $dataProviderMethodCases = null;
  57. /**
  58. * @var array<class-string, Tokens>
  59. */
  60. private static array $tokensCache = [];
  61. public static function tearDownAfterClass(): void
  62. {
  63. self::$srcClassCases = null;
  64. self::$testClassCases = null;
  65. self::$tokensCache = [];
  66. }
  67. /**
  68. * @dataProvider provideThatSrcClassHaveTestClassCases
  69. */
  70. public function testThatSrcClassHaveTestClass(string $className): void
  71. {
  72. $testClassName = 'PhpCsFixer\Tests'.substr($className, 10).'Test';
  73. self::assertTrue(class_exists($testClassName), \sprintf('Expected test class "%s" for "%s" not found.', $testClassName, $className));
  74. }
  75. /**
  76. * @dataProvider provideThatSrcClassesNotAbuseInterfacesCases
  77. *
  78. * @param class-string $className
  79. */
  80. public function testThatSrcClassesNotAbuseInterfaces(string $className): void
  81. {
  82. $rc = new \ReflectionClass($className);
  83. $allowedMethods = array_map(
  84. fn (\ReflectionClass $interface): array => $this->getPublicMethodNames($interface),
  85. $rc->getInterfaces()
  86. );
  87. if (\count($allowedMethods) > 0) {
  88. $allowedMethods = array_unique(array_merge(...array_values($allowedMethods)));
  89. }
  90. $allowedMethods[] = '__construct';
  91. $allowedMethods[] = '__destruct';
  92. $allowedMethods[] = '__wakeup';
  93. $exceptionMethods = [
  94. 'configure', // due to AbstractFixer::configure
  95. 'getConfigurationDefinition', // due to AbstractFixer::getConfigurationDefinition
  96. 'getDefaultConfiguration', // due to AbstractFixer::getDefaultConfiguration
  97. 'setWhitespacesConfig', // due to AbstractFixer::setWhitespacesConfig
  98. 'createConfigurationDefinition', // due to AbstractProxyFixer calling `createConfigurationDefinition` of proxied rule
  99. ];
  100. $definedMethods = $this->getPublicMethodNames($rc);
  101. $extraMethods = array_diff(
  102. $definedMethods,
  103. $allowedMethods,
  104. $exceptionMethods
  105. );
  106. sort($extraMethods);
  107. self::assertEmpty(
  108. $extraMethods,
  109. \sprintf(
  110. "Class '%s' should not have public methods that are not part of implemented interfaces.\nViolations:\n%s",
  111. $className,
  112. implode("\n", array_map(static fn (string $item): string => " * {$item}", $extraMethods))
  113. )
  114. );
  115. }
  116. /**
  117. * @dataProvider provideSrcClassCases
  118. *
  119. * @param class-string $className
  120. */
  121. public function testThatSrcClassesNotExposeProperties(string $className): void
  122. {
  123. $rc = new \ReflectionClass($className);
  124. self::assertEmpty(
  125. $rc->getProperties(\ReflectionProperty::IS_PUBLIC),
  126. \sprintf('Class \'%s\' should not have public properties.', $className)
  127. );
  128. if ($rc->isFinal()) {
  129. return;
  130. }
  131. $allowedProps = [];
  132. $definedProps = $rc->getProperties(\ReflectionProperty::IS_PROTECTED);
  133. if (false !== $rc->getParentClass()) {
  134. $allowedProps = $rc->getParentClass()->getProperties(\ReflectionProperty::IS_PROTECTED);
  135. }
  136. $allowedProps = array_map(static fn (\ReflectionProperty $item): string => $item->getName(), $allowedProps);
  137. $definedProps = array_map(static fn (\ReflectionProperty $item): string => $item->getName(), $definedProps);
  138. $exceptionPropsPerClass = [
  139. AbstractFixer::class => ['configuration', 'configurationDefinition', 'whitespacesConfig'],
  140. AbstractPhpdocToTypeDeclarationFixer::class => ['configuration'],
  141. AbstractPhpdocTypesFixer::class => ['tags'],
  142. AbstractProxyFixer::class => ['proxyFixers'],
  143. ConfigurableFixerTrait::class => ['configuration'],
  144. FixCommand::class => ['defaultDescription', 'defaultName'],
  145. ];
  146. $extraProps = array_diff(
  147. $definedProps,
  148. $allowedProps,
  149. $exceptionPropsPerClass[$className] ?? []
  150. );
  151. sort($extraProps);
  152. self::assertEmpty(
  153. $extraProps,
  154. \sprintf(
  155. "Class '%s' should not have protected properties.\nViolations:\n%s",
  156. $className,
  157. implode("\n", array_map(static fn (string $item): string => " * {$item}", $extraProps))
  158. )
  159. );
  160. }
  161. /**
  162. * @dataProvider provideTestClassCases
  163. */
  164. public function testThatTestClassExtendsPhpCsFixerTestCaseClass(string $className): void
  165. {
  166. self::assertTrue(is_subclass_of($className, TestCase::class), \sprintf('Expected test class "%s" to be a subclass of "%s".', $className, TestCase::class));
  167. }
  168. /**
  169. * @dataProvider provideTestClassCases
  170. *
  171. * @param class-string<TestCase> $testClassName
  172. */
  173. public function testThatTestClassesAreTraitOrAbstractOrFinal(string $testClassName): void
  174. {
  175. $rc = new \ReflectionClass($testClassName);
  176. self::assertTrue(
  177. $rc->isTrait() || $rc->isAbstract() || $rc->isFinal(),
  178. \sprintf('Test class %s should be trait, abstract or final.', $testClassName)
  179. );
  180. }
  181. /**
  182. * @dataProvider provideTestClassCases
  183. *
  184. * @param class-string<TestCase> $testClassName
  185. */
  186. public function testThatTestClassesAreInternal(string $testClassName): void
  187. {
  188. $rc = new \ReflectionClass($testClassName);
  189. $doc = new DocBlock($rc->getDocComment());
  190. self::assertNotEmpty(
  191. $doc->getAnnotationsOfType('internal'),
  192. \sprintf('Test class %s should have internal annotation.', $testClassName)
  193. );
  194. }
  195. /**
  196. * @dataProvider provideTestClassCases
  197. *
  198. * @param class-string<TestCase> $testClassName
  199. */
  200. public function testThatTestClassesPublicMethodsAreCorrectlyNamed(string $testClassName): void
  201. {
  202. $reflectionClass = new \ReflectionClass($testClassName);
  203. $publicMethods = array_filter(
  204. $reflectionClass->getMethods(\ReflectionMethod::IS_PUBLIC),
  205. static fn (\ReflectionMethod $reflectionMethod): bool => $reflectionMethod->getDeclaringClass()->getName() === $reflectionClass->getName()
  206. );
  207. if ([] === $publicMethods) {
  208. $this->expectNotToPerformAssertions(); // no methods to test, all good!
  209. return;
  210. }
  211. foreach ($publicMethods as $method) {
  212. self::assertMatchesRegularExpression(
  213. '/^(test|expect|provide|setUpBeforeClass$|tearDownAfterClass$|__construct$)/',
  214. $method->getName(),
  215. \sprintf('Public method "%s::%s" is not properly named.', $reflectionClass->getName(), $method->getName())
  216. );
  217. }
  218. }
  219. /**
  220. * @dataProvider provideDataProviderMethodCases
  221. *
  222. * @param class-string<TestCase> $testClassName
  223. */
  224. public function testThatTestDataProvidersAreUsed(string $testClassName, string $dataProviderName): void
  225. {
  226. $usedDataProviderMethodNames = [];
  227. foreach ($this->getUsedDataProviderMethodNames($testClassName) as $providerName) {
  228. $usedDataProviderMethodNames[] = $providerName;
  229. }
  230. self::assertContains(
  231. $dataProviderName,
  232. $usedDataProviderMethodNames,
  233. \sprintf('Data provider "%s::%s" is not used.', $testClassName, $dataProviderName),
  234. );
  235. }
  236. /**
  237. * @dataProvider provideDataProviderMethodCases
  238. */
  239. public function testThatTestDataProvidersAreCorrectlyNamed(string $testClassName, string $dataProviderName): void
  240. {
  241. self::assertMatchesRegularExpression('/^provide[A-Z]\S+Cases$/', $dataProviderName, \sprintf(
  242. 'Data provider "%s::%s" is not correctly named.',
  243. $testClassName,
  244. $dataProviderName,
  245. ));
  246. }
  247. public static function provideDataProviderMethodCases(): iterable
  248. {
  249. if (null === self::$dataProviderMethodCases) {
  250. self::$dataProviderMethodCases = [];
  251. foreach (self::provideTestClassCases() as $testClassName) {
  252. $testClassName = reset($testClassName);
  253. $reflectionClass = new \ReflectionClass($testClassName);
  254. $dataProviderNames = array_filter(
  255. $reflectionClass->getMethods(\ReflectionMethod::IS_PUBLIC),
  256. static fn (\ReflectionMethod $reflectionMethod): bool => $reflectionMethod->getDeclaringClass()->getName() === $reflectionClass->getName() && str_starts_with($reflectionMethod->getName(), 'provide')
  257. );
  258. foreach ($dataProviderNames as $dataProviderName) {
  259. self::$dataProviderMethodCases[$testClassName.'::'.$dataProviderName->getName()] = [$testClassName, $dataProviderName->getName()];
  260. }
  261. }
  262. }
  263. yield from self::$dataProviderMethodCases;
  264. }
  265. /**
  266. * @dataProvider provideTestClassCases
  267. *
  268. * @param class-string<TestCase> $testClassName
  269. */
  270. public function testThatTestClassCoversAreCorrect(string $testClassName): void
  271. {
  272. $reflectionClass = new \ReflectionClass($testClassName);
  273. if ($reflectionClass->isAbstract() || $reflectionClass->isInterface()) {
  274. $this->expectNotToPerformAssertions();
  275. return;
  276. }
  277. $doc = $reflectionClass->getDocComment();
  278. self::assertNotFalse($doc);
  279. if (Preg::match('/@coversNothing/', $doc)) {
  280. return;
  281. }
  282. $covers = Preg::matchAll('/@covers (\S*)/', $doc, $matches);
  283. self::assertGreaterThanOrEqual(1, $covers, \sprintf('Missing @covers in PHPDoc of test class "%s".', $testClassName));
  284. array_shift($matches);
  285. /** @var class-string $class */
  286. $class = '\\'.str_replace('PhpCsFixer\Tests\\', 'PhpCsFixer\\', substr($testClassName, 0, -4));
  287. $parentClass = (new \ReflectionClass($class))->getParentClass();
  288. $parentClassName = false === $parentClass ? null : '\\'.$parentClass->getName();
  289. foreach ($matches as $match) {
  290. $classMatch = array_shift($match);
  291. self::assertTrue(
  292. $classMatch === $class || $parentClassName === $classMatch,
  293. \sprintf('Unexpected @covers "%s" for "%s".', $classMatch, $testClassName)
  294. );
  295. }
  296. }
  297. /**
  298. * @dataProvider provideSrcClassCases
  299. * @dataProvider provideTestClassCases
  300. *
  301. * @param class-string $className
  302. */
  303. public function testThereIsNoUsageOfExtract(string $className): void
  304. {
  305. $calledFunctions = $this->extractFunctionNamesCalledInClass($className);
  306. $message = \sprintf('Class %s must not use "extract()", explicitly extract only the keys that are needed - you never know what\'s else inside.', $className);
  307. self::assertNotContains('extract', $calledFunctions, $message);
  308. }
  309. /**
  310. * @dataProvider provideThereIsNoPregFunctionUsedDirectlyCases
  311. *
  312. * @param class-string $className
  313. */
  314. public function testThereIsNoPregFunctionUsedDirectly(string $className): void
  315. {
  316. $calledFunctions = $this->extractFunctionNamesCalledInClass($className);
  317. $message = \sprintf('Class %s must not use preg_*, it shall use Preg::* instead.', $className);
  318. self::assertNotContains('preg_filter', $calledFunctions, $message);
  319. self::assertNotContains('preg_grep', $calledFunctions, $message);
  320. self::assertNotContains('preg_match', $calledFunctions, $message);
  321. self::assertNotContains('preg_match_all', $calledFunctions, $message);
  322. self::assertNotContains('preg_replace', $calledFunctions, $message);
  323. self::assertNotContains('preg_replace_callback', $calledFunctions, $message);
  324. self::assertNotContains('preg_split', $calledFunctions, $message);
  325. }
  326. /**
  327. * @dataProvider provideTestClassCases
  328. *
  329. * @param class-string<TestCase> $className
  330. */
  331. public function testNoPHPUnitMockUsed(string $className): void
  332. {
  333. $calledFunctions = $this->extractFunctionNamesCalledInClass($className);
  334. $message = \sprintf('Class %s must not use PHPUnit\'s mock, it shall use anonymous class instead.', $className);
  335. self::assertNotContains('getMockBuilder', $calledFunctions, $message);
  336. self::assertNotContains('createMock', $calledFunctions, $message);
  337. self::assertNotContains('createMockForIntersectionOfInterfaces', $calledFunctions, $message);
  338. self::assertNotContains('createPartialMock', $calledFunctions, $message);
  339. self::assertNotContains('createTestProxy', $calledFunctions, $message);
  340. self::assertNotContains('getMockForAbstractClass', $calledFunctions, $message);
  341. self::assertNotContains('getMockFromWsdl', $calledFunctions, $message);
  342. self::assertNotContains('getMockForTrait', $calledFunctions, $message);
  343. self::assertNotContains('getMockClass', $calledFunctions, $message);
  344. self::assertNotContains('createConfiguredMock', $calledFunctions, $message);
  345. self::assertNotContains('getObjectForTrait', $calledFunctions, $message);
  346. }
  347. /**
  348. * @dataProvider provideTestClassCases
  349. *
  350. * @param class-string<TestCase> $testClassName
  351. */
  352. public function testExpectedInputOrder(string $testClassName): void
  353. {
  354. $reflectionClass = new \ReflectionClass($testClassName);
  355. $publicMethods = array_filter(
  356. $reflectionClass->getMethods(\ReflectionMethod::IS_PUBLIC),
  357. static fn (\ReflectionMethod $reflectionMethod): bool => $reflectionMethod->getDeclaringClass()->getName() === $reflectionClass->getName()
  358. );
  359. if ([] === $publicMethods) {
  360. $this->expectNotToPerformAssertions(); // no methods to test, all good!
  361. return;
  362. }
  363. /** @var \ReflectionMethod $method */
  364. foreach ($publicMethods as $method) {
  365. $parameters = $method->getParameters();
  366. if (\count($parameters) < 2) {
  367. $this->addToAssertionCount(1); // not enough parameters to test, all good!
  368. continue;
  369. }
  370. $expected = [
  371. 'expected' => false,
  372. 'input' => false,
  373. ];
  374. for ($i = \count($parameters) - 1; $i >= 0; --$i) {
  375. $name = $parameters[$i]->getName();
  376. if (isset($expected[$name])) {
  377. $expected[$name] = $i;
  378. }
  379. }
  380. $expected = array_filter($expected, static fn ($item): bool => false !== $item);
  381. if (\count($expected) < 2) {
  382. $this->addToAssertionCount(1); // not enough parameters to test, all good!
  383. continue;
  384. }
  385. self::assertLessThan(
  386. $expected['input'],
  387. $expected['expected'],
  388. \sprintf('Public method "%s::%s" has parameter \'input\' before \'expected\'.', $reflectionClass->getName(), $method->getName())
  389. );
  390. }
  391. }
  392. /**
  393. * @dataProvider provideDataProviderMethodCases
  394. *
  395. * @param class-string<TestCase> $testClassName
  396. */
  397. public function testDataProvidersAreNonPhpVersionConditional(string $testClassName, string $dataProviderName): void
  398. {
  399. $tokens = $this->createTokensForClass($testClassName);
  400. $tokensAnalyzer = new TokensAnalyzer($tokens);
  401. $dataProviderElements = array_filter($tokensAnalyzer->getClassyElements(), static function (array $v, int $k) use ($tokens, $dataProviderName) {
  402. $nextToken = $tokens[$tokens->getNextMeaningfulToken($k)];
  403. // element is data provider method
  404. return 'method' === $v['type'] && $nextToken->equals([T_STRING, $dataProviderName]);
  405. }, ARRAY_FILTER_USE_BOTH);
  406. if (1 !== \count($dataProviderElements)) {
  407. throw new \UnexpectedValueException(\sprintf('Data provider "%s::%s" should be found exactly once, got %d times.', $testClassName, $dataProviderName, \count($dataProviderElements)));
  408. }
  409. $methodIndex = array_key_first($dataProviderElements);
  410. $startIndex = $tokens->getNextTokenOfKind($methodIndex, ['{']);
  411. $endIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $startIndex);
  412. $versionTokens = array_filter($tokens->findGivenKind(T_STRING, $startIndex, $endIndex), static function (Token $v): bool {
  413. return $v->equalsAny([
  414. [T_STRING, 'PHP_VERSION_ID'],
  415. [T_STRING, 'PHP_MAJOR_VERSION'],
  416. [T_STRING, 'PHP_MINOR_VERSION'],
  417. [T_STRING, 'PHP_RELEASE_VERSION'],
  418. [T_STRING, 'phpversion'],
  419. ], false);
  420. });
  421. self::assertCount(
  422. 0,
  423. $versionTokens,
  424. \sprintf(
  425. 'Data provider "%s::%s" should not check PHP version and provide different cases depends on it. It leads to situation when DataProvider provides "sometimes 10, sometimes 11" test cases, depends on PHP version. That makes John Doe to see "you run 10/10" and thinking all tests are executed, instead of actually seeing "you run 10/11 and 1 skipped".',
  426. $testClassName,
  427. $dataProviderName,
  428. ),
  429. );
  430. }
  431. /**
  432. * @dataProvider provideDataProviderMethodCases
  433. */
  434. public function testDataProvidersDeclaredReturnType(string $testClassName, string $dataProviderName): void
  435. {
  436. $dataProvider = new \ReflectionMethod($testClassName, $dataProviderName);
  437. self::assertSame('iterable', $dataProvider->hasReturnType() && $dataProvider->getReturnType() instanceof \ReflectionNamedType ? $dataProvider->getReturnType()->getName() : null, \sprintf('Data provider "%s::%s" must provide `iterable` as return in method prototype.', $testClassName, $dataProviderName));
  438. $doc = new DocBlock(false !== $dataProvider->getDocComment() ? $dataProvider->getDocComment() : '/** */');
  439. $returnDocs = $doc->getAnnotationsOfType('return');
  440. if (\count($returnDocs) > 1) {
  441. throw new \UnexpectedValueException(\sprintf('Multiple "%s::%s@return" annotations.', $testClassName, $dataProviderName));
  442. }
  443. if (1 !== \count($returnDocs)) {
  444. $this->addToAssertionCount(1); // no @return annotation, all good!
  445. return;
  446. }
  447. $returnDoc = $returnDocs[0];
  448. $types = $returnDoc->getTypes();
  449. self::assertCount(1, $types, \sprintf('Data provider "%s::%s@return" must provide single type.', $testClassName, $dataProviderName));
  450. self::assertMatchesRegularExpression('/^iterable\</', $types[0], \sprintf('Data provider "%s::%s@return" must return iterable.', $testClassName, $dataProviderName));
  451. self::assertMatchesRegularExpression('/^iterable\<(?:(?:int\|)?string, )?array\{/', $types[0], \sprintf('Data provider "%s::%s@return" must return iterable of tuples (eg `iterable<string, array{string, string}>`).', $testClassName, $dataProviderName));
  452. }
  453. /**
  454. * @dataProvider provideSrcClassCases
  455. * @dataProvider provideTestClassCases
  456. *
  457. * @param class-string $className
  458. */
  459. public function testAllCodeContainSingleClassy(string $className): void
  460. {
  461. $headerTypes = [
  462. T_ABSTRACT,
  463. T_AS,
  464. T_COMMENT,
  465. T_DECLARE,
  466. T_DOC_COMMENT,
  467. T_FINAL,
  468. T_LNUMBER,
  469. T_NAMESPACE,
  470. T_NS_SEPARATOR,
  471. T_OPEN_TAG,
  472. T_STRING,
  473. T_USE,
  474. T_WHITESPACE,
  475. ];
  476. $tokens = $this->createTokensForClass($className);
  477. $classyIndex = null;
  478. self::assertTrue($tokens->isAnyTokenKindsFound(Token::getClassyTokenKinds()), \sprintf('File for "%s" should contains a classy.', $className));
  479. $count = \count($tokens);
  480. for ($index = 1; $index < $count; ++$index) {
  481. if ($tokens[$index]->isClassy()) {
  482. $classyIndex = $index;
  483. break;
  484. }
  485. if (\defined('T_ATTRIBUTE') && $tokens[$index]->isGivenKind(T_ATTRIBUTE)) {
  486. $index = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_ATTRIBUTE, $index);
  487. continue;
  488. }
  489. if (!$tokens[$index]->isGivenKind($headerTypes) && !$tokens[$index]->equalsAny([';', '=', '(', ')'])) {
  490. self::fail(\sprintf('File for "%s" should only contains single classy, found "%s" @ %d.', $className, $tokens[$index]->toJson(), $index));
  491. }
  492. }
  493. self::assertNotNull($classyIndex, \sprintf('File for "%s" does not contain a classy.', $className));
  494. $nextTokenOfKind = $tokens->getNextTokenOfKind($classyIndex, ['{']);
  495. if (!\is_int($nextTokenOfKind)) {
  496. throw new \UnexpectedValueException('Classy without {} - braces.');
  497. }
  498. $classyEndIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $nextTokenOfKind);
  499. self::assertNull($tokens->getNextMeaningfulToken($classyEndIndex), \sprintf('File for "%s" should only contains a single classy.', $className));
  500. }
  501. /**
  502. * @dataProvider provideSrcClassCases
  503. *
  504. * @param class-string $className
  505. */
  506. public function testInheritdocIsNotAbused(string $className): void
  507. {
  508. $rc = new \ReflectionClass($className);
  509. $allowedMethods = array_map(
  510. fn (\ReflectionClass $interface): array => $this->getPublicMethodNames($interface),
  511. $rc->getInterfaces()
  512. );
  513. if (\count($allowedMethods) > 0) {
  514. $allowedMethods = array_merge(...array_values($allowedMethods));
  515. }
  516. $parentClass = $rc;
  517. while (false !== $parentClass = $parentClass->getParentClass()) {
  518. foreach ($parentClass->getMethods(\ReflectionMethod::IS_PUBLIC | \ReflectionMethod::IS_PROTECTED) as $method) {
  519. $allowedMethods[] = $method->getName();
  520. }
  521. }
  522. $allowedMethods = array_unique($allowedMethods);
  523. $methodsWithInheritdoc = array_filter(
  524. $rc->getMethods(),
  525. static fn (\ReflectionMethod $rm): bool => false !== $rm->getDocComment() && stripos($rm->getDocComment(), '@inheritdoc')
  526. );
  527. $methodsWithInheritdoc = array_map(
  528. static fn (\ReflectionMethod $rm): string => $rm->getName(),
  529. $methodsWithInheritdoc
  530. );
  531. $extraMethods = array_diff($methodsWithInheritdoc, $allowedMethods);
  532. self::assertEmpty(
  533. $extraMethods,
  534. \sprintf(
  535. "Class '%s' should not have methods with '@inheritdoc' in PHPDoc that are not inheriting PHPDoc.\nViolations:\n%s",
  536. $className,
  537. implode("\n", array_map(static fn ($item): string => " * {$item}", $extraMethods))
  538. )
  539. );
  540. }
  541. /**
  542. * @return iterable<string, array{class-string}>
  543. */
  544. public static function provideSrcClassCases(): iterable
  545. {
  546. if (null === self::$srcClassCases) {
  547. $cases = self::getSrcClasses();
  548. self::$srcClassCases = array_combine(
  549. $cases,
  550. array_map(static fn (string $case): array => [$case], $cases),
  551. );
  552. }
  553. yield from self::$srcClassCases;
  554. }
  555. public static function provideThatSrcClassesNotAbuseInterfacesCases(): iterable
  556. {
  557. return array_map(
  558. static fn (string $item): array => [$item],
  559. array_filter(self::getSrcClasses(), static function (string $className): bool {
  560. $rc = new \ReflectionClass($className);
  561. $doc = false !== $rc->getDocComment()
  562. ? new DocBlock($rc->getDocComment())
  563. : null;
  564. if (
  565. $rc->isInterface()
  566. || (null !== $doc && \count($doc->getAnnotationsOfType('internal')) > 0)
  567. || \in_array($className, [
  568. \PhpCsFixer\Finder::class,
  569. AbstractFixerTestCase::class,
  570. AbstractIntegrationTestCase::class,
  571. Tokens::class,
  572. ], true)
  573. ) {
  574. return false;
  575. }
  576. $interfaces = $rc->getInterfaces();
  577. $interfacesCount = \count($interfaces);
  578. if (0 === $interfacesCount) {
  579. return false;
  580. }
  581. if (1 === $interfacesCount) {
  582. $interface = reset($interfaces);
  583. if (\Stringable::class === $interface->getName()) {
  584. return false;
  585. }
  586. }
  587. return true;
  588. })
  589. );
  590. }
  591. public static function provideThatSrcClassHaveTestClassCases(): iterable
  592. {
  593. return array_map(
  594. static fn (string $item): array => [$item],
  595. array_filter(
  596. self::getSrcClasses(),
  597. static function (string $className): bool {
  598. $rc = new \ReflectionClass($className);
  599. return !$rc->isTrait() && !$rc->isAbstract() && !$rc->isInterface() && \count($rc->getMethods(\ReflectionMethod::IS_PUBLIC)) > 0;
  600. }
  601. )
  602. );
  603. }
  604. public function testAllTestsForShortOpenTagAreHandled(): void
  605. {
  606. $testClassesWithShortOpenTag = array_filter(
  607. self::getTestClasses(),
  608. fn (string $className): bool => str_contains($this->getFileContentForClass($className), 'short_open_tag') && self::class !== $className
  609. );
  610. $testFilesWithShortOpenTag = array_map(
  611. fn (string $className): string => './'.$this->getFilePathForClass($className),
  612. $testClassesWithShortOpenTag
  613. );
  614. $phpunitXmlContent = file_get_contents(__DIR__.'/../../phpunit.xml.dist');
  615. $phpunitFiles = (array) simplexml_load_string($phpunitXmlContent)->xpath('testsuites/testsuite[@name="short-open-tag"]')[0]->file;
  616. sort($testFilesWithShortOpenTag);
  617. sort($phpunitFiles);
  618. self::assertSame($testFilesWithShortOpenTag, $phpunitFiles);
  619. }
  620. /**
  621. * @dataProvider provideTestClassCases
  622. *
  623. * @param class-string $className
  624. */
  625. public function testThatTestMethodsAreNotDuplicated(string $className): void
  626. {
  627. $class = new \ReflectionClass($className);
  628. $alreadyFoundMethods = [];
  629. $duplicates = [];
  630. foreach ($class->getMethods(\ReflectionMethod::IS_PUBLIC) as $method) {
  631. if (!str_starts_with($method->getName(), 'test')) {
  632. continue;
  633. }
  634. $startLine = (int) $method->getStartLine();
  635. $length = (int) $method->getEndLine() - $startLine;
  636. if (3 === $length) { // open and closing brace are included - this checks for single line methods
  637. continue;
  638. }
  639. /** @var list<string> $source */
  640. $source = file((string) $method->getFileName());
  641. $candidateContent = implode('', \array_slice($source, $startLine, $length));
  642. if (str_contains($candidateContent, '$this->doTest(')) {
  643. continue;
  644. }
  645. $foundInDuplicates = false;
  646. foreach ($alreadyFoundMethods as $methodKey => $methodContent) {
  647. if ($candidateContent === $methodContent) {
  648. $duplicates[] = \sprintf('%s is duplicate of %s', $methodKey, $method->getName());
  649. $foundInDuplicates = true;
  650. }
  651. }
  652. if (!$foundInDuplicates) {
  653. $alreadyFoundMethods[$method->getName()] = $candidateContent;
  654. }
  655. }
  656. self::assertSame(
  657. [],
  658. $duplicates,
  659. \sprintf(
  660. "Duplicated methods found in %s:\n - %s",
  661. $className,
  662. implode("\n - ", $duplicates)
  663. )
  664. );
  665. }
  666. /**
  667. * @dataProvider provideDataProviderMethodCases
  668. *
  669. * @param class-string<TestCase> $testClassName
  670. */
  671. public function testThatDataFromDataProvidersIsNotDuplicated(string $testClassName, string $dataProviderName): void
  672. {
  673. $exceptions = [ // should only shrink
  674. // because of Serialization
  675. 'PhpCsFixer\Tests\AutoReview\CommandTest::provideCommandHasNameConstCases',
  676. 'PhpCsFixer\Tests\AutoReview\DocumentationTest::provideFixerDocumentationFileIsUpToDateCases',
  677. 'PhpCsFixer\Tests\AutoReview\FixerFactoryTest::providePriorityIntegrationTestFilesAreListedInPriorityGraphCases',
  678. 'PhpCsFixer\Tests\Console\Command\DescribeCommandTest::provideExecuteOutputCases',
  679. 'PhpCsFixer\Tests\Console\Command\HelpCommandTest::provideGetDisplayableAllowedValuesCases',
  680. 'PhpCsFixer\Tests\Documentation\FixerDocumentGeneratorTest::provideGenerateRuleSetsDocumentationCases',
  681. 'PhpCsFixer\Tests\Fixer\Basic\EncodingFixerTest::provideFixCases',
  682. 'PhpCsFixer\Tests\UtilsTest::provideStableSortCases',
  683. // because of more than one duplicate
  684. 'PhpCsFixer\Tests\Console\Command\SelfUpdateCommandTest::provideExecuteCases',
  685. 'PhpCsFixer\Tests\Console\Output\Progress\DotsOutputTest::provideDotsProgressOutputCases',
  686. 'PhpCsFixer\Tests\Fixer\ArrayNotation\TrimArraySpacesFixerTest::provideFixCases',
  687. 'PhpCsFixer\Tests\Fixer\Basic\BracesFixerTest::provideFixClassyBracesCases',
  688. 'PhpCsFixer\Tests\Fixer\Basic\BracesPositionFixerTest::provideFixCases',
  689. 'PhpCsFixer\Tests\Fixer\Basic\CurlyBracesPositionFixerTest::provideFixCases',
  690. 'PhpCsFixer\Tests\Fixer\ClassNotation\FinalClassFixerTest::provideFix80Cases',
  691. 'PhpCsFixer\Tests\Fixer\Basic\PsrAutoloadingFixerTest::provideFixCases',
  692. 'PhpCsFixer\Tests\Fixer\CastNotation\LowercaseCastFixerTest::provideFixCases',
  693. 'PhpCsFixer\Tests\Fixer\ClassNotation\FinalClassFixerTest::provideFix82Cases',
  694. 'PhpCsFixer\Tests\Fixer\ControlStructure\NoUnneededControlParenthesesFixerTest::provideFixAllCases',
  695. 'PhpCsFixer\Tests\Fixer\ControlStructure\NoUselessElseFixerTest::provideFixIfElseIfElseCases',
  696. 'PhpCsFixer\Tests\Fixer\ControlStructure\NoUselessElseFixerTest::provideFixIfElseCases',
  697. 'PhpCsFixer\Tests\Fixer\ControlStructure\YodaStyleFixerTest::provideFixCases',
  698. 'PhpCsFixer\Tests\Fixer\ControlStructure\YodaStyleFixerTest::providePHP71Cases',
  699. 'PhpCsFixer\Tests\Fixer\DoctrineAnnotation\DoctrineAnnotationArrayAssignmentFixerTest::provideFixCases',
  700. 'PhpCsFixer\Tests\Fixer\DoctrineAnnotation\DoctrineAnnotationArrayAssignmentFixerTest::provideFixWithColonCases',
  701. 'PhpCsFixer\Tests\Fixer\DoctrineAnnotation\DoctrineAnnotationSpacesFixerTest::provideFixAroundParenthesesOnlyCases',
  702. 'PhpCsFixer\Tests\Fixer\LanguageConstruct\SingleSpaceAfterConstructFixerTest::provideFixWithUseCases',
  703. 'PhpCsFixer\Tests\Fixer\LanguageConstruct\SingleSpaceAroundConstructFixerTest::provideFixWithUseCases',
  704. 'PhpCsFixer\Tests\Fixer\PhpUnit\PhpUnitStrictFixerTest::provideFixCases',
  705. 'PhpCsFixer\Tests\Fixer\Phpdoc\PhpdocInlineTagNormalizerFixerTest::provideFixCases',
  706. 'PhpCsFixer\Tests\Tokenizer\Analyzer\AlternativeSyntaxAnalyzerTest::provideItThrowsOnInvalidAlternativeSyntaxBlockStartIndexCases',
  707. 'PhpCsFixer\Tests\Tokenizer\Analyzer\FunctionsAnalyzerTest::provideIsGlobalFunctionCallCases',
  708. 'PhpCsFixer\Tests\Tokenizer\TokenTest::provideIsMagicConstantCases',
  709. 'PhpCsFixer\Tests\Tokenizer\TokensAnalyzerTest::provideIsBinaryOperatorCases',
  710. // because of one duplicate
  711. 'PhpCsFixer\Tests\DocBlock\TypeExpressionTest::provideGetTypesCases',
  712. 'PhpCsFixer\Tests\DocBlock\TypeExpressionTest::provideGetConstTypesCases',
  713. 'PhpCsFixer\Tests\DocBlock\TypeExpressionTest::provideParseInvalidExceptionCases',
  714. 'PhpCsFixer\Tests\FixerNameValidatorTest::provideIsValidCases',
  715. 'PhpCsFixer\Tests\Fixer\ArrayNotation\ArraySyntaxFixerTest::provideFixCases',
  716. 'PhpCsFixer\Tests\Fixer\Basic\BracesFixerTest::provideFixMultiLineStructuresCases',
  717. 'PhpCsFixer\Tests\Fixer\Basic\BracesFixerTest::provideFunctionImportCases',
  718. 'PhpCsFixer\Tests\Fixer\Comment\NoEmptyCommentFixerTest::provideFixCases',
  719. 'PhpCsFixer\Tests\Fixer\ConstantNotation\NativeConstantInvocationFixerTest::provideFixWithDefaultConfigurationCases',
  720. 'PhpCsFixer\Tests\Fixer\ControlStructure\NoBreakCommentFixerTest::provideFixCases',
  721. 'PhpCsFixer\Tests\Fixer\ControlStructure\NoBreakCommentFixerTest::provideFixWithDifferentCommentTextCases',
  722. 'PhpCsFixer\Tests\Fixer\ControlStructure\NoBreakCommentFixerTest::provideFixWithDifferentLineEndingCases',
  723. 'PhpCsFixer\Tests\Fixer\DoctrineAnnotation\DoctrineAnnotationSpacesFixerTest::provideFixAllCases',
  724. 'PhpCsFixer\Tests\Fixer\DoctrineAnnotation\DoctrineAnnotationSpacesFixerTest::provideFixAroundCommasOnlyCases',
  725. 'PhpCsFixer\Tests\Fixer\FunctionNotation\PhpdocToParamTypeFixerTest::provideFixCases',
  726. 'PhpCsFixer\Tests\Fixer\Import\OrderedImportsFixerTest::provideFixCases',
  727. 'PhpCsFixer\Tests\Fixer\Operator\StandardizeIncrementFixerTest::provideFixCases',
  728. 'PhpCsFixer\Tests\Fixer\PhpUnit\PhpUnitTargetVersionTest::provideFulfillsCases',
  729. 'PhpCsFixer\Tests\Fixer\Phpdoc\PhpdocTypesOrderFixerTest::provideFixWithNullFirstCases',
  730. 'PhpCsFixer\Tests\Fixer\StringNotation\SingleQuoteFixerTest::provideFixCases',
  731. 'PhpCsFixer\Tests\Fixer\Whitespace\MethodChainingIndentationFixerTest::provideFixCases',
  732. 'PhpCsFixer\Tests\Fixer\Whitespace\SpacesInsideParenthesesFixerTest::provideDefaultFixCases',
  733. 'PhpCsFixer\Tests\Fixer\Whitespace\SpacesInsideParenthesesFixerTest::provideSpacesFixCases',
  734. 'PhpCsFixer\Tests\Tokenizer\Analyzer\AttributeAnalyzerTest::provideIsAttributeCases',
  735. 'PhpCsFixer\Tests\Tokenizer\Analyzer\ClassyAnalyzerTest::provideIsClassyInvocationCases',
  736. 'PhpCsFixer\Tests\Tokenizer\Transformer\ReturnRefTransformerTest::provideProcessCases',
  737. ];
  738. if (\in_array($testClassName.'::'.$dataProviderName, $exceptions, true)) {
  739. $this->addToAssertionCount(1);
  740. return;
  741. }
  742. $dataProvider = new \ReflectionMethod($testClassName, $dataProviderName);
  743. $duplicates = [];
  744. $alreadyFoundCases = [];
  745. foreach ($dataProvider->invoke($dataProvider->getDeclaringClass()->newInstanceWithoutConstructor()) as $candidateKey => $candidateData) {
  746. $candidateData = serialize($candidateData);
  747. $foundInDuplicates = false;
  748. foreach ($alreadyFoundCases as $caseKey => $caseData) {
  749. if ($candidateData === $caseData) {
  750. $duplicates[] = \sprintf(
  751. 'Duplicate in %s::%s: %s and %s.'.PHP_EOL,
  752. $testClassName,
  753. $dataProviderName,
  754. \is_int($caseKey) ? '#'.$caseKey : '"'.$caseKey.'"',
  755. \is_int($candidateKey) ? '#'.$candidateKey : '"'.$candidateKey.'"',
  756. );
  757. $foundInDuplicates = true;
  758. }
  759. }
  760. if (!$foundInDuplicates) {
  761. $alreadyFoundCases[$candidateKey] = $candidateData;
  762. }
  763. }
  764. self::assertSame([], $duplicates);
  765. }
  766. /**
  767. * @return iterable<string, array{class-string<TestCase>}>
  768. */
  769. public static function provideTestClassCases(): iterable
  770. {
  771. if (null === self::$testClassCases) {
  772. $cases = self::getTestClasses();
  773. self::$testClassCases = array_combine(
  774. $cases,
  775. array_map(static fn (string $case): array => [$case], $cases),
  776. );
  777. }
  778. yield from self::$testClassCases;
  779. }
  780. public static function provideThereIsNoPregFunctionUsedDirectlyCases(): iterable
  781. {
  782. return array_map(
  783. static fn (string $item): array => [$item],
  784. array_filter(
  785. self::getSrcClasses(),
  786. static fn (string $className): bool => Preg::class !== $className,
  787. ),
  788. );
  789. }
  790. /**
  791. * @dataProvider providePhpUnitFixerExtendsAbstractPhpUnitFixerCases
  792. *
  793. * @param class-string $className
  794. */
  795. public function testPhpUnitFixerExtendsAbstractPhpUnitFixer(string $className): void
  796. {
  797. $reflection = new \ReflectionClass($className);
  798. self::assertTrue($reflection->isSubclassOf(AbstractPhpUnitFixer::class));
  799. }
  800. public static function providePhpUnitFixerExtendsAbstractPhpUnitFixerCases(): iterable
  801. {
  802. $factory = new FixerFactory();
  803. $factory->registerBuiltInFixers();
  804. foreach ($factory->getFixers() as $fixer) {
  805. if (!str_starts_with($fixer->getName(), 'php_unit_')) {
  806. continue;
  807. }
  808. // this one fixes usage of PHPUnit classes
  809. if ($fixer instanceof PhpUnitNamespacedFixer) {
  810. continue;
  811. }
  812. if ($fixer instanceof AbstractProxyFixer) {
  813. continue;
  814. }
  815. yield [\get_class($fixer)];
  816. }
  817. }
  818. /**
  819. * @dataProvider provideSrcClassCases
  820. * @dataProvider provideTestClassCases
  821. *
  822. * @param class-string $className
  823. */
  824. public function testConstantsAreInUpperCase(string $className): void
  825. {
  826. $rc = new \ReflectionClass($className);
  827. $reflectionClassConstants = $rc->getReflectionConstants();
  828. if (\count($reflectionClassConstants) < 1) {
  829. $this->expectNotToPerformAssertions();
  830. return;
  831. }
  832. foreach ($reflectionClassConstants as $constant) {
  833. $constantName = $constant->getName();
  834. self::assertSame(strtoupper($constantName), $constantName, $className);
  835. }
  836. }
  837. /**
  838. * @param class-string $className
  839. *
  840. * @return list<string>
  841. */
  842. private function extractFunctionNamesCalledInClass(string $className): array
  843. {
  844. $tokens = $this->createTokensForClass($className);
  845. $stringTokens = array_filter(
  846. $tokens->toArray(),
  847. static fn (Token $token): bool => $token->isGivenKind(T_STRING)
  848. );
  849. $strings = array_map(
  850. static fn (Token $token): string => $token->getContent(),
  851. $stringTokens
  852. );
  853. return array_unique($strings);
  854. }
  855. /**
  856. * @param class-string $className
  857. */
  858. private function getFilePathForClass(string $className): string
  859. {
  860. $file = $className;
  861. $file = preg_replace('#^PhpCsFixer\\\Tests\\\#', 'tests\\', $file);
  862. $file = preg_replace('#^PhpCsFixer\\\#', 'src\\', $file);
  863. return str_replace('\\', \DIRECTORY_SEPARATOR, $file).'.php';
  864. }
  865. /**
  866. * @param class-string $className
  867. */
  868. private function getFileContentForClass(string $className): string
  869. {
  870. return file_get_contents($this->getFilePathForClass($className));
  871. }
  872. /**
  873. * @param class-string $className
  874. */
  875. private function createTokensForClass(string $className): Tokens
  876. {
  877. if (!isset(self::$tokensCache[$className])) {
  878. self::$tokensCache[$className] = Tokens::fromCode(self::getFileContentForClass($className));
  879. }
  880. return self::$tokensCache[$className];
  881. }
  882. /**
  883. * @param class-string<TestCase> $testClassName
  884. *
  885. * @return iterable<string, string>
  886. */
  887. private function getUsedDataProviderMethodNames(string $testClassName): iterable
  888. {
  889. foreach ($this->getAnnotationsOfTestClass($testClassName, 'dataProvider') as $methodName => $dataProviderAnnotation) {
  890. if (1 === preg_match('/@dataProvider\s+(?P<methodName>\w+)/', $dataProviderAnnotation->getContent(), $matches)) {
  891. yield $methodName => $matches['methodName'];
  892. }
  893. }
  894. }
  895. /**
  896. * @param class-string<TestCase> $testClassName
  897. *
  898. * @return iterable<string, Annotation>
  899. */
  900. private function getAnnotationsOfTestClass(string $testClassName, string $annotation): iterable
  901. {
  902. $tokens = $this->createTokensForClass($testClassName);
  903. foreach ($tokens as $index => $token) {
  904. if (!$token->isGivenKind(T_DOC_COMMENT)) {
  905. continue;
  906. }
  907. $methodName = $tokens[$tokens->getNextTokenOfKind($index, [[T_STRING]])]->getContent();
  908. $docBlock = new DocBlock($token->getContent());
  909. $dataProviderAnnotations = $docBlock->getAnnotationsOfType($annotation);
  910. foreach ($dataProviderAnnotations as $dataProviderAnnotation) {
  911. yield $methodName => $dataProviderAnnotation;
  912. }
  913. }
  914. }
  915. /**
  916. * @return list<class-string>
  917. */
  918. private static function getSrcClasses(): array
  919. {
  920. static $classes;
  921. if (null !== $classes) {
  922. return $classes;
  923. }
  924. $finder = Finder::create()
  925. ->files()
  926. ->name('*.php')
  927. ->in(__DIR__.'/../../src')
  928. ->exclude([
  929. 'Resources',
  930. ])
  931. ;
  932. /** @var list<class-string> $classes */
  933. $classes = array_map(
  934. static fn (SplFileInfo $file): string => \sprintf(
  935. '%s\%s%s%s',
  936. 'PhpCsFixer',
  937. strtr($file->getRelativePath(), \DIRECTORY_SEPARATOR, '\\'),
  938. '' !== $file->getRelativePath() ? '\\' : '',
  939. $file->getBasename('.'.$file->getExtension())
  940. ),
  941. iterator_to_array($finder, false)
  942. );
  943. sort($classes);
  944. return $classes;
  945. }
  946. /**
  947. * @return list<class-string<TestCase>>
  948. */
  949. private static function getTestClasses(): array
  950. {
  951. static $classes;
  952. if (null !== $classes) {
  953. return $classes;
  954. }
  955. $finder = Finder::create()
  956. ->files()
  957. ->name('*Test.php')
  958. ->in(__DIR__.'/..')
  959. ->exclude([
  960. 'Fixtures',
  961. ])
  962. ;
  963. /** @var list<class-string<TestCase>> $classes */
  964. $classes = array_map(
  965. static fn (SplFileInfo $file): string => \sprintf(
  966. 'PhpCsFixer\Tests\%s%s%s',
  967. strtr($file->getRelativePath(), \DIRECTORY_SEPARATOR, '\\'),
  968. '' !== $file->getRelativePath() ? '\\' : '',
  969. $file->getBasename('.'.$file->getExtension())
  970. ),
  971. iterator_to_array($finder, false)
  972. );
  973. sort($classes);
  974. return $classes;
  975. }
  976. /**
  977. * @param \ReflectionClass<object> $rc
  978. *
  979. * @return list<string>
  980. */
  981. private function getPublicMethodNames(\ReflectionClass $rc): array
  982. {
  983. return array_map(
  984. static fn (\ReflectionMethod $rm): string => $rm->getName(),
  985. $rc->getMethods(\ReflectionMethod::IS_PUBLIC)
  986. );
  987. }
  988. }