ConfigurationResolverTest.php 46 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515
  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\Console;
  13. use PhpCsFixer\AbstractFixer;
  14. use PhpCsFixer\Cache\NullCacheManager;
  15. use PhpCsFixer\Config;
  16. use PhpCsFixer\ConfigInterface;
  17. use PhpCsFixer\ConfigurationException\InvalidConfigurationException;
  18. use PhpCsFixer\Console\Command\FixCommand;
  19. use PhpCsFixer\Console\ConfigurationResolver;
  20. use PhpCsFixer\Console\Output\Progress\ProgressOutputType;
  21. use PhpCsFixer\Differ\NullDiffer;
  22. use PhpCsFixer\Differ\UnifiedDiffer;
  23. use PhpCsFixer\Finder;
  24. use PhpCsFixer\Fixer\ConfigurableFixerInterface;
  25. use PhpCsFixer\Fixer\ConfigurableFixerTrait;
  26. use PhpCsFixer\Fixer\DeprecatedFixerInterface;
  27. use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver;
  28. use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface;
  29. use PhpCsFixer\FixerConfiguration\FixerOptionBuilder;
  30. use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
  31. use PhpCsFixer\Runner\Parallel\ParallelConfig;
  32. use PhpCsFixer\Runner\Parallel\ParallelConfigFactory;
  33. use PhpCsFixer\Tests\TestCase;
  34. use PhpCsFixer\Tokenizer\Tokens;
  35. use PhpCsFixer\ToolInfoInterface;
  36. use PhpCsFixer\Utils;
  37. use Symfony\Component\Console\Output\OutputInterface;
  38. /**
  39. * @author Katsuhiro Ogawa <ko.fivestar@gmail.com>
  40. * @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
  41. *
  42. * @internal
  43. *
  44. * @covers \PhpCsFixer\Console\ConfigurationResolver
  45. */
  46. final class ConfigurationResolverTest extends TestCase
  47. {
  48. public function testResolveParallelConfig(): void
  49. {
  50. $parallelConfig = new ParallelConfig();
  51. $config = (new Config())->setParallelConfig($parallelConfig);
  52. $resolver = $this->createConfigurationResolver([], $config);
  53. self::assertSame($parallelConfig, $resolver->getParallelConfig());
  54. }
  55. public function testDefaultParallelConfigFallbacksToSequential(): void
  56. {
  57. $parallelConfig = $this->createConfigurationResolver([])->getParallelConfig();
  58. $defaultParallelConfig = ParallelConfigFactory::sequential();
  59. self::assertSame($defaultParallelConfig->getMaxProcesses(), $parallelConfig->getMaxProcesses());
  60. self::assertSame($defaultParallelConfig->getFilesPerProcess(), $parallelConfig->getFilesPerProcess());
  61. self::assertSame($defaultParallelConfig->getProcessTimeout(), $parallelConfig->getProcessTimeout());
  62. }
  63. public function testCliSequentialOptionOverridesParallelConfig(): void
  64. {
  65. $config = (new Config())->setParallelConfig(new ParallelConfig(10));
  66. $resolver = $this->createConfigurationResolver(['sequential' => true], $config);
  67. self::assertSame(1, $resolver->getParallelConfig()->getMaxProcesses());
  68. }
  69. public function testSetOptionWithUndefinedOption(): void
  70. {
  71. $this->expectException(InvalidConfigurationException::class);
  72. $this->expectExceptionMessageMatches('/^Unknown option name: "foo"\.$/');
  73. $this->createConfigurationResolver(['foo' => 'bar']);
  74. }
  75. public function testResolveProgressWithPositiveConfigAndPositiveOption(): void
  76. {
  77. $config = new Config();
  78. $config->setHideProgress(true);
  79. $resolver = $this->createConfigurationResolver([
  80. 'format' => 'txt',
  81. 'verbosity' => OutputInterface::VERBOSITY_VERBOSE,
  82. ], $config);
  83. self::assertSame('none', $resolver->getProgressType());
  84. }
  85. public function testResolveProgressWithPositiveConfigAndNegativeOption(): void
  86. {
  87. $config = new Config();
  88. $config->setHideProgress(true);
  89. $resolver = $this->createConfigurationResolver([
  90. 'format' => 'txt',
  91. 'verbosity' => OutputInterface::VERBOSITY_NORMAL,
  92. ], $config);
  93. self::assertSame('none', $resolver->getProgressType());
  94. }
  95. public function testResolveProgressWithNegativeConfigAndPositiveOption(): void
  96. {
  97. $config = new Config();
  98. $config->setHideProgress(false);
  99. $resolver = $this->createConfigurationResolver([
  100. 'format' => 'txt',
  101. 'verbosity' => OutputInterface::VERBOSITY_VERBOSE,
  102. ], $config);
  103. self::assertSame('bar', $resolver->getProgressType());
  104. }
  105. public function testResolveProgressWithNegativeConfigAndNegativeOption(): void
  106. {
  107. $config = new Config();
  108. $config->setHideProgress(false);
  109. $resolver = $this->createConfigurationResolver([
  110. 'format' => 'txt',
  111. 'verbosity' => OutputInterface::VERBOSITY_NORMAL,
  112. ], $config);
  113. self::assertSame('bar', $resolver->getProgressType());
  114. }
  115. /**
  116. * @dataProvider provideProgressTypeCases
  117. */
  118. public function testResolveProgressWithPositiveConfigAndExplicitProgress(string $progressType): void
  119. {
  120. $config = new Config();
  121. $config->setHideProgress(true);
  122. $resolver = $this->createConfigurationResolver([
  123. 'format' => 'txt',
  124. 'verbosity' => OutputInterface::VERBOSITY_VERBOSE,
  125. 'show-progress' => $progressType,
  126. ], $config);
  127. self::assertSame($progressType, $resolver->getProgressType());
  128. }
  129. /**
  130. * @dataProvider provideProgressTypeCases
  131. */
  132. public function testResolveProgressWithNegativeConfigAndExplicitProgress(string $progressType): void
  133. {
  134. $config = new Config();
  135. $config->setHideProgress(false);
  136. $resolver = $this->createConfigurationResolver([
  137. 'format' => 'txt',
  138. 'verbosity' => OutputInterface::VERBOSITY_VERBOSE,
  139. 'show-progress' => $progressType,
  140. ], $config);
  141. self::assertSame($progressType, $resolver->getProgressType());
  142. }
  143. /**
  144. * @return iterable<string, array{0: ProgressOutputType::*}>
  145. */
  146. public static function provideProgressTypeCases(): iterable
  147. {
  148. foreach (ProgressOutputType::all() as $outputType) {
  149. yield $outputType => [$outputType];
  150. }
  151. }
  152. public function testResolveProgressWithInvalidExplicitProgress(): void
  153. {
  154. $resolver = $this->createConfigurationResolver([
  155. 'format' => 'txt',
  156. 'verbosity' => OutputInterface::VERBOSITY_VERBOSE,
  157. 'show-progress' => 'foo',
  158. ]);
  159. $this->expectException(InvalidConfigurationException::class);
  160. $this->expectExceptionMessage('The progress type "foo" is not defined, supported are "bar", "dots" and "none".');
  161. $resolver->getProgressType();
  162. }
  163. public function testResolveConfigFileDefault(): void
  164. {
  165. $resolver = $this->createConfigurationResolver([]);
  166. self::assertNull($resolver->getConfigFile());
  167. }
  168. public function testResolveConfigFileByPathOfFile(): void
  169. {
  170. $dir = __DIR__.'/../Fixtures/ConfigurationResolverConfigFile/case_1';
  171. $resolver = $this->createConfigurationResolver(['path' => [$dir.\DIRECTORY_SEPARATOR.'foo.php']]);
  172. self::assertSame($dir.\DIRECTORY_SEPARATOR.'.php-cs-fixer.dist.php', $resolver->getConfigFile());
  173. self::assertInstanceOf(\Test1Config::class, $resolver->getConfig()); // @phpstan-ignore-line to avoid `Class Test1Config not found.`
  174. }
  175. public function testResolveConfigFileSpecified(): void
  176. {
  177. $file = __DIR__.'/../Fixtures/ConfigurationResolverConfigFile/case_4/my.php-cs-fixer.php';
  178. $resolver = $this->createConfigurationResolver(['config' => $file]);
  179. self::assertSame($file, $resolver->getConfigFile());
  180. self::assertInstanceOf(\Test4Config::class, $resolver->getConfig()); // @phpstan-ignore-line to avoid `Class Test4Config not found.`
  181. }
  182. /**
  183. * @dataProvider provideResolveConfigFileChooseFileCases
  184. *
  185. * @param class-string<ConfigInterface> $expectedClass
  186. */
  187. public function testResolveConfigFileChooseFile(string $expectedFile, string $expectedClass, string $path, ?string $cwdPath = null): void
  188. {
  189. $resolver = $this->createConfigurationResolver(
  190. ['path' => [$path]],
  191. null,
  192. $cwdPath ?? ''
  193. );
  194. self::assertSame($expectedFile, $resolver->getConfigFile());
  195. self::assertInstanceOf($expectedClass, $resolver->getConfig());
  196. }
  197. /**
  198. * @return iterable<array{0: string, 1: string, 2: string, 3?: string}>
  199. */
  200. public static function provideResolveConfigFileChooseFileCases(): iterable
  201. {
  202. $dirBase = self::getFixtureDir();
  203. yield [
  204. $dirBase.'case_1'.\DIRECTORY_SEPARATOR.'.php-cs-fixer.dist.php',
  205. 'Test1Config',
  206. $dirBase.'case_1',
  207. ];
  208. yield [
  209. $dirBase.'case_2'.\DIRECTORY_SEPARATOR.'.php-cs-fixer.php',
  210. 'Test2Config',
  211. $dirBase.'case_2',
  212. ];
  213. yield [
  214. $dirBase.'case_3'.\DIRECTORY_SEPARATOR.'.php-cs-fixer.php',
  215. 'Test3Config',
  216. $dirBase.'case_3',
  217. ];
  218. yield [
  219. $dirBase.'case_6'.\DIRECTORY_SEPARATOR.'.php-cs-fixer.dist.php',
  220. 'Test6Config',
  221. $dirBase.'case_6'.\DIRECTORY_SEPARATOR.'subdir',
  222. $dirBase.'case_6',
  223. ];
  224. yield [
  225. $dirBase.'case_6'.\DIRECTORY_SEPARATOR.'.php-cs-fixer.dist.php',
  226. 'Test6Config',
  227. $dirBase.'case_6'.\DIRECTORY_SEPARATOR.'subdir/empty_file.php',
  228. $dirBase.'case_6',
  229. ];
  230. }
  231. public function testResolveConfigFileChooseFileWithInvalidFile(): void
  232. {
  233. $this->expectException(InvalidConfigurationException::class);
  234. $this->expectExceptionMessageMatches(
  235. '#^The config file: ".+[\/\\\]Fixtures[\/\\\]ConfigurationResolverConfigFile[\/\\\]case_5[\/\\\]\.php-cs-fixer\.dist\.php" does not return a "PhpCsFixer\\\ConfigInterface" instance\. Got: "string"\.$#'
  236. );
  237. $dirBase = self::getFixtureDir();
  238. $resolver = $this->createConfigurationResolver(['path' => [$dirBase.'case_5']]);
  239. $resolver->getConfig();
  240. }
  241. public function testResolveConfigFileChooseFileWithInvalidFormat(): void
  242. {
  243. $this->expectException(InvalidConfigurationException::class);
  244. $this->expectExceptionMessageMatches('/^The format "xls" is not defined, supported are "checkstyle", "gitlab", "json", "junit", "txt" and "xml"\.$/');
  245. $dirBase = self::getFixtureDir();
  246. $resolver = $this->createConfigurationResolver(['path' => [$dirBase.'case_7']]);
  247. $resolver->getReporter();
  248. }
  249. public function testResolveConfigFileChooseFileWithPathArrayWithoutConfig(): void
  250. {
  251. $this->expectException(InvalidConfigurationException::class);
  252. $this->expectExceptionMessageMatches('/^For multiple paths config parameter is required\.$/');
  253. $dirBase = self::getFixtureDir();
  254. $resolver = $this->createConfigurationResolver(['path' => [$dirBase.'case_1/.php-cs-fixer.dist.php', $dirBase.'case_1/foo.php']]);
  255. $resolver->getConfig();
  256. }
  257. public function testResolveConfigFileChooseFileWithPathArrayAndConfig(): void
  258. {
  259. $dirBase = self::getFixtureDir();
  260. $configFile = $dirBase.'case_1/.php-cs-fixer.dist.php';
  261. $resolver = $this->createConfigurationResolver([
  262. 'config' => $configFile,
  263. 'path' => [$configFile, $dirBase.'case_1/foo.php'],
  264. ]);
  265. self::assertSame($configFile, $resolver->getConfigFile());
  266. }
  267. /**
  268. * @param array<int, string> $paths
  269. * @param array<int, string> $expectedPaths
  270. *
  271. * @dataProvider provideResolvePathCases
  272. */
  273. public function testResolvePath(array $paths, string $cwd, array $expectedPaths): void
  274. {
  275. $resolver = $this->createConfigurationResolver(
  276. ['path' => $paths],
  277. null,
  278. $cwd
  279. );
  280. self::assertSame($expectedPaths, $resolver->getPath());
  281. }
  282. public static function provideResolvePathCases(): iterable
  283. {
  284. yield [
  285. ['Command'],
  286. __DIR__,
  287. [__DIR__.\DIRECTORY_SEPARATOR.'Command'],
  288. ];
  289. yield [
  290. [basename(__DIR__)],
  291. \dirname(__DIR__),
  292. [__DIR__],
  293. ];
  294. yield [
  295. [' Command'],
  296. __DIR__,
  297. [__DIR__.\DIRECTORY_SEPARATOR.'Command'],
  298. ];
  299. yield [
  300. ['Command '],
  301. __DIR__,
  302. [__DIR__.\DIRECTORY_SEPARATOR.'Command'],
  303. ];
  304. }
  305. /**
  306. * @param list<string> $paths
  307. *
  308. * @dataProvider provideRejectInvalidPathCases
  309. */
  310. public function testRejectInvalidPath(array $paths, string $expectedMessage): void
  311. {
  312. $resolver = $this->createConfigurationResolver(
  313. ['path' => $paths],
  314. null,
  315. \dirname(__DIR__)
  316. );
  317. $this->expectException(InvalidConfigurationException::class);
  318. $this->expectExceptionMessage($expectedMessage);
  319. $resolver->getPath();
  320. }
  321. public static function provideRejectInvalidPathCases(): iterable
  322. {
  323. yield [
  324. [''],
  325. 'Invalid path: "".',
  326. ];
  327. yield [
  328. [__DIR__, ''],
  329. 'Invalid path: "".',
  330. ];
  331. yield [
  332. ['', __DIR__],
  333. 'Invalid path: "".',
  334. ];
  335. yield [
  336. [' '],
  337. 'Invalid path: " ".',
  338. ];
  339. yield [
  340. [__DIR__, ' '],
  341. 'Invalid path: " ".',
  342. ];
  343. yield [
  344. [' ', __DIR__],
  345. 'Invalid path: " ".',
  346. ];
  347. }
  348. public function testResolvePathWithFileThatIsExcludedDirectlyOverridePathMode(): void
  349. {
  350. $config = new Config();
  351. $config->getFinder()
  352. ->in(__DIR__)
  353. ->notPath(basename(__FILE__))
  354. ;
  355. $resolver = $this->createConfigurationResolver(
  356. ['path' => [__FILE__]],
  357. $config
  358. );
  359. self::assertCount(1, $resolver->getFinder());
  360. }
  361. public function testResolvePathWithFileThatIsExcludedDirectlyIntersectionPathMode(): void
  362. {
  363. $config = new Config();
  364. $config->getFinder()
  365. ->in(__DIR__)
  366. ->notPath(basename(__FILE__))
  367. ;
  368. $resolver = $this->createConfigurationResolver([
  369. 'path' => [__FILE__],
  370. 'path-mode' => 'intersection',
  371. ], $config);
  372. self::assertCount(0, $resolver->getFinder());
  373. }
  374. public function testResolvePathWithFileThatIsExcludedByDirOverridePathMode(): void
  375. {
  376. $dir = \dirname(__DIR__);
  377. $config = new Config();
  378. $config->getFinder()
  379. ->in($dir)
  380. ->exclude(basename(__DIR__))
  381. ;
  382. $resolver = $this->createConfigurationResolver(
  383. ['path' => [__FILE__]],
  384. $config
  385. );
  386. self::assertCount(1, $resolver->getFinder());
  387. }
  388. public function testResolvePathWithFileThatIsExcludedByDirIntersectionPathMode(): void
  389. {
  390. $dir = \dirname(__DIR__);
  391. $config = new Config();
  392. $config->getFinder()
  393. ->in($dir)
  394. ->exclude(basename(__DIR__))
  395. ;
  396. $resolver = $this->createConfigurationResolver([
  397. 'path-mode' => 'intersection',
  398. 'path' => [__FILE__],
  399. ], $config);
  400. self::assertCount(0, $resolver->getFinder());
  401. }
  402. public function testResolvePathWithFileThatIsNotExcluded(): void
  403. {
  404. $dir = __DIR__;
  405. $config = new Config();
  406. $config->getFinder()
  407. ->in($dir)
  408. ->notPath('foo-'.basename(__FILE__))
  409. ;
  410. $resolver = $this->createConfigurationResolver(
  411. ['path' => [__FILE__]],
  412. $config
  413. );
  414. self::assertCount(1, $resolver->getFinder());
  415. }
  416. /**
  417. * @param \Exception|list<string> $expected
  418. * @param list<string> $path
  419. *
  420. * @dataProvider provideResolveIntersectionOfPathsCases
  421. */
  422. public function testResolveIntersectionOfPaths($expected, ?Finder $configFinder, array $path, string $pathMode, ?string $configOption = null): void
  423. {
  424. if ($expected instanceof \Exception) {
  425. $this->expectException(\get_class($expected));
  426. }
  427. if (null !== $configFinder) {
  428. $config = new Config();
  429. $config->setFinder($configFinder);
  430. } else {
  431. $config = null;
  432. }
  433. $resolver = $this->createConfigurationResolver([
  434. 'config' => $configOption,
  435. 'path' => $path,
  436. 'path-mode' => $pathMode,
  437. ], $config);
  438. $intersectionItems = array_map(
  439. static fn (\SplFileInfo $file): string => $file->getRealPath(),
  440. iterator_to_array($resolver->getFinder(), false)
  441. );
  442. sort($expected);
  443. sort($intersectionItems);
  444. self::assertSame($expected, $intersectionItems);
  445. }
  446. public static function provideResolveIntersectionOfPathsCases(): iterable
  447. {
  448. $dir = __DIR__.'/../Fixtures/ConfigurationResolverPathsIntersection';
  449. $cb = static fn (array $items): array => array_map(
  450. static fn (string $item): string => realpath($dir.'/'.$item),
  451. $items
  452. );
  453. yield 'no path at all' => [
  454. new \LogicException(),
  455. Finder::create(),
  456. [],
  457. 'override',
  458. ];
  459. yield 'configured only by finder' => [
  460. // don't override if the argument is empty
  461. $cb(['a1.php', 'a2.php', 'b/b1.php', 'b/b2.php', 'b_b/b_b1.php', 'c/c1.php', 'c/d/cd1.php', 'd/d1.php', 'd/d2.php', 'd/e/de1.php', 'd/f/df1.php']),
  462. Finder::create()
  463. ->in($dir),
  464. [],
  465. 'override',
  466. ];
  467. yield 'configured only by argument' => [
  468. $cb(['a1.php', 'a2.php', 'b/b1.php', 'b/b2.php', 'b_b/b_b1.php', 'c/c1.php', 'c/d/cd1.php', 'd/d1.php', 'd/d2.php', 'd/e/de1.php', 'd/f/df1.php']),
  469. Finder::create(),
  470. [$dir],
  471. 'override',
  472. ];
  473. yield 'configured by finder, intersected with empty argument' => [
  474. [],
  475. Finder::create()
  476. ->in($dir),
  477. [],
  478. 'intersection',
  479. ];
  480. yield 'configured by finder, intersected with dir' => [
  481. $cb(['c/c1.php', 'c/d/cd1.php']),
  482. Finder::create()
  483. ->in($dir),
  484. [$dir.'/c'],
  485. 'intersection',
  486. ];
  487. yield 'configured by finder, intersected with file' => [
  488. $cb(['c/c1.php']),
  489. Finder::create()
  490. ->in($dir),
  491. [$dir.'/c/c1.php'],
  492. 'intersection',
  493. ];
  494. yield 'finder points to one dir while argument to another, not connected' => [
  495. [],
  496. Finder::create()
  497. ->in($dir.'/b'),
  498. [$dir.'/c'],
  499. 'intersection',
  500. ];
  501. yield 'finder with excluded dir, intersected with excluded file' => [
  502. [],
  503. Finder::create()
  504. ->in($dir)
  505. ->exclude('c'),
  506. [$dir.'/c/d/cd1.php'],
  507. 'intersection',
  508. ];
  509. yield 'finder with excluded dir, intersected with dir containing excluded one' => [
  510. $cb(['c/c1.php']),
  511. Finder::create()
  512. ->in($dir)
  513. ->exclude('c/d'),
  514. [$dir.'/c'],
  515. 'intersection',
  516. ];
  517. yield 'finder with excluded file, intersected with dir containing excluded one' => [
  518. $cb(['c/d/cd1.php']),
  519. Finder::create()
  520. ->in($dir)
  521. ->notPath('c/c1.php'),
  522. [$dir.'/c'],
  523. 'intersection',
  524. ];
  525. yield 'configured by finder, intersected with non-existing path' => [
  526. new \LogicException(),
  527. Finder::create()
  528. ->in($dir),
  529. ['non_existing_dir'],
  530. 'intersection',
  531. ];
  532. yield 'configured by config file, overridden by multiple files' => [
  533. $cb(['d/d1.php', 'd/d2.php']),
  534. null,
  535. [$dir.'/d/d1.php', $dir.'/d/d2.php'],
  536. 'override',
  537. $dir.'/d/.php-cs-fixer.php',
  538. ];
  539. yield 'configured by config file, intersected with multiple files' => [
  540. $cb(['d/d1.php', 'd/d2.php']),
  541. null,
  542. [$dir.'/d/d1.php', $dir.'/d/d2.php'],
  543. 'intersection',
  544. $dir.'/d/.php-cs-fixer.php',
  545. ];
  546. yield 'configured by config file, overridden by non-existing dir' => [
  547. new \LogicException(),
  548. null,
  549. [$dir.'/d/fff'],
  550. 'override',
  551. $dir.'/d/.php-cs-fixer.php',
  552. ];
  553. yield 'configured by config file, intersected with non-existing dir' => [
  554. new \LogicException(),
  555. null,
  556. [$dir.'/d/fff'],
  557. 'intersection',
  558. $dir.'/d/.php-cs-fixer.php',
  559. ];
  560. yield 'configured by config file, overridden by non-existing file' => [
  561. new \LogicException(),
  562. null,
  563. [$dir.'/d/fff.php'],
  564. 'override',
  565. $dir.'/d/.php-cs-fixer.php',
  566. ];
  567. yield 'configured by config file, intersected with non-existing file' => [
  568. new \LogicException(),
  569. null,
  570. [$dir.'/d/fff.php'],
  571. 'intersection',
  572. $dir.'/d/.php-cs-fixer.php',
  573. ];
  574. yield 'configured by config file, overridden by multiple files and dirs' => [
  575. $cb(['d/d1.php', 'd/e/de1.php', 'd/f/df1.php']),
  576. null,
  577. [$dir.'/d/d1.php', $dir.'/d/e', $dir.'/d/f/'],
  578. 'override',
  579. $dir.'/d/.php-cs-fixer.php',
  580. ];
  581. yield 'configured by config file, intersected with multiple files and dirs' => [
  582. $cb(['d/d1.php', 'd/e/de1.php', 'd/f/df1.php']),
  583. null,
  584. [$dir.'/d/d1.php', $dir.'/d/e', $dir.'/d/f/'],
  585. 'intersection',
  586. $dir.'/d/.php-cs-fixer.php',
  587. ];
  588. }
  589. /**
  590. * @param array<string, mixed> $options
  591. *
  592. * @dataProvider provideConfigFinderIsOverriddenCases
  593. */
  594. public function testConfigFinderIsOverridden(array $options, bool $expectedResult): void
  595. {
  596. $resolver = $this->createConfigurationResolver($options);
  597. self::assertSame($expectedResult, $resolver->configFinderIsOverridden());
  598. $resolver = $this->createConfigurationResolver($options);
  599. $resolver->getFinder();
  600. self::assertSame($expectedResult, $resolver->configFinderIsOverridden());
  601. }
  602. public static function provideConfigFinderIsOverriddenCases(): iterable
  603. {
  604. $root = __DIR__.'/../..';
  605. yield [
  606. [
  607. 'config' => $root.'/.php-cs-fixer.dist.php',
  608. ],
  609. false,
  610. ];
  611. yield [
  612. [
  613. 'config' => $root.'/.php-cs-fixer.dist.php',
  614. 'path' => [$root.'/src'],
  615. ],
  616. true,
  617. ];
  618. yield [
  619. [],
  620. false,
  621. ];
  622. yield [
  623. [
  624. 'path' => [$root.'/src'],
  625. ],
  626. false,
  627. ];
  628. yield [
  629. [
  630. 'config' => $root.'/.php-cs-fixer.dist.php',
  631. 'path' => [$root.'/src'],
  632. 'path-mode' => ConfigurationResolver::PATH_MODE_INTERSECTION,
  633. ],
  634. false,
  635. ];
  636. // scenario when loaded config is not setting custom finder
  637. yield [
  638. [
  639. 'config' => $root.'/tests/Fixtures/ConfigurationResolverConfigFile/case_3/.php-cs-fixer.dist.php',
  640. 'path' => [$root.'/src'],
  641. ],
  642. false,
  643. ];
  644. // scenario when loaded config contains not fully defined finder
  645. yield [
  646. [
  647. 'config' => $root.'/tests/Fixtures/ConfigurationResolverConfigFile/case_9/.php-cs-fixer.php',
  648. 'path' => [$root.'/src'],
  649. ],
  650. false,
  651. ];
  652. }
  653. public function testResolveIsDryRunViaStdIn(): void
  654. {
  655. $resolver = $this->createConfigurationResolver([
  656. 'dry-run' => false,
  657. 'path' => ['-'],
  658. ]);
  659. self::assertTrue($resolver->isDryRun());
  660. }
  661. public function testResolveIsDryRunViaNegativeOption(): void
  662. {
  663. $resolver = $this->createConfigurationResolver(['dry-run' => false]);
  664. self::assertFalse($resolver->isDryRun());
  665. }
  666. public function testResolveIsDryRunViaPositiveOption(): void
  667. {
  668. $resolver = $this->createConfigurationResolver(['dry-run' => true]);
  669. self::assertTrue($resolver->isDryRun());
  670. }
  671. /**
  672. * @dataProvider provideResolveBooleanOptionCases
  673. */
  674. public function testResolveUsingCacheWithConfigOption(bool $expected, bool $configValue, ?string $passed): void
  675. {
  676. $config = new Config();
  677. $config->setUsingCache($configValue);
  678. $resolver = $this->createConfigurationResolver(
  679. ['using-cache' => $passed],
  680. $config
  681. );
  682. self::assertSame($expected, $resolver->getUsingCache());
  683. }
  684. public function testResolveUsingCacheWithPositiveConfigAndNoOption(): void
  685. {
  686. $config = new Config();
  687. $config->setUsingCache(true);
  688. $resolver = $this->createConfigurationResolver(
  689. [],
  690. $config
  691. );
  692. self::assertTrue($resolver->getUsingCache());
  693. }
  694. public function testResolveUsingCacheWithNegativeConfigAndNoOption(): void
  695. {
  696. $config = new Config();
  697. $config->setUsingCache(false);
  698. $resolver = $this->createConfigurationResolver(
  699. [],
  700. $config
  701. );
  702. self::assertFalse($resolver->getUsingCache());
  703. }
  704. /**
  705. * @dataProvider provideResolveUsingCacheForRuntimesCases
  706. */
  707. public function testResolveUsingCacheForRuntimes(bool $cacheAllowed, bool $installedWithComposer, bool $asPhar, bool $inDocker): void
  708. {
  709. $config = new Config();
  710. $config->setUsingCache(true);
  711. $resolver = $this->createConfigurationResolver(
  712. [],
  713. $config,
  714. '',
  715. new class($installedWithComposer, $asPhar, $inDocker) implements ToolInfoInterface {
  716. private bool $installedWithComposer;
  717. private bool $asPhar;
  718. private bool $inDocker;
  719. public function __construct(bool $installedWithComposer, bool $asPhar, bool $inDocker)
  720. {
  721. $this->installedWithComposer = $installedWithComposer;
  722. $this->asPhar = $asPhar;
  723. $this->inDocker = $inDocker;
  724. }
  725. public function getComposerInstallationDetails(): array
  726. {
  727. throw new \BadMethodCallException();
  728. }
  729. public function getComposerVersion(): string
  730. {
  731. throw new \BadMethodCallException();
  732. }
  733. public function getVersion(): string
  734. {
  735. throw new \BadMethodCallException();
  736. }
  737. public function isInstalledAsPhar(): bool
  738. {
  739. return $this->asPhar;
  740. }
  741. public function isInstalledByComposer(): bool
  742. {
  743. return $this->installedWithComposer;
  744. }
  745. public function isRunInsideDocker(): bool
  746. {
  747. return $this->inDocker;
  748. }
  749. public function getPharDownloadUri(string $version): string
  750. {
  751. throw new \BadMethodCallException();
  752. }
  753. }
  754. );
  755. self::assertSame($cacheAllowed, $resolver->getUsingCache());
  756. }
  757. /**
  758. * @return iterable<array{0: bool, 1: bool, 2: bool, 3: bool}>
  759. */
  760. public static function provideResolveUsingCacheForRuntimesCases(): iterable
  761. {
  762. yield 'none of the allowed runtimes' => [false, false, false, false];
  763. yield 'composer installation' => [true, true, false, false];
  764. yield 'PHAR distribution' => [true, false, true, false];
  765. yield 'Docker runtime' => [true, false, false, true];
  766. }
  767. public function testResolveCacheFileWithoutConfigAndOption(): void
  768. {
  769. $config = new Config();
  770. $default = $config->getCacheFile();
  771. $resolver = $this->createConfigurationResolver(
  772. [],
  773. $config
  774. );
  775. self::assertSame($default, $resolver->getCacheFile());
  776. }
  777. public function testResolveCacheFileWithConfig(): void
  778. {
  779. $cacheFile = 'foo/bar.baz';
  780. $config = new Config();
  781. $config
  782. ->setUsingCache(false)
  783. ->setCacheFile($cacheFile)
  784. ;
  785. $resolver = $this->createConfigurationResolver(
  786. [],
  787. $config
  788. );
  789. self::assertNull($resolver->getCacheFile());
  790. $cacheManager = $resolver->getCacheManager();
  791. self::assertInstanceOf(NullCacheManager::class, $cacheManager);
  792. self::assertFalse($resolver->getLinter()->isAsync());
  793. }
  794. public function testResolveCacheFileWithOption(): void
  795. {
  796. $cacheFile = 'bar.baz';
  797. $config = new Config();
  798. $config->setCacheFile($cacheFile);
  799. $resolver = $this->createConfigurationResolver(
  800. ['cache-file' => $cacheFile],
  801. $config
  802. );
  803. self::assertSame($cacheFile, $resolver->getCacheFile());
  804. }
  805. public function testResolveCacheFileWithConfigAndOption(): void
  806. {
  807. $configCacheFile = 'foo/bar.baz';
  808. $optionCacheFile = 'bar.baz';
  809. $config = new Config();
  810. $config->setCacheFile($configCacheFile);
  811. $resolver = $this->createConfigurationResolver(
  812. ['cache-file' => $optionCacheFile],
  813. $config
  814. );
  815. self::assertSame($optionCacheFile, $resolver->getCacheFile());
  816. }
  817. /**
  818. * @dataProvider provideResolveBooleanOptionCases
  819. */
  820. public function testResolveAllowRiskyWithConfigOption(bool $expected, bool $configValue, ?string $passed): void
  821. {
  822. $config = new Config();
  823. $config->setRiskyAllowed($configValue);
  824. $resolver = $this->createConfigurationResolver(
  825. ['allow-risky' => $passed],
  826. $config
  827. );
  828. self::assertSame($expected, $resolver->getRiskyAllowed());
  829. }
  830. public function testResolveAllowRiskyWithNegativeConfigAndPositiveOption(): void
  831. {
  832. $config = new Config();
  833. $config->setRiskyAllowed(false);
  834. $resolver = $this->createConfigurationResolver(
  835. ['allow-risky' => 'yes'],
  836. $config
  837. );
  838. self::assertTrue($resolver->getRiskyAllowed());
  839. }
  840. public function testResolveAllowRiskyWithNegativeConfigAndNegativeOption(): void
  841. {
  842. $config = new Config();
  843. $config->setRiskyAllowed(false);
  844. $resolver = $this->createConfigurationResolver(
  845. ['allow-risky' => 'no'],
  846. $config
  847. );
  848. self::assertFalse($resolver->getRiskyAllowed());
  849. }
  850. public function testResolveAllowRiskyWithPositiveConfigAndNoOption(): void
  851. {
  852. $config = new Config();
  853. $config->setRiskyAllowed(true);
  854. $resolver = $this->createConfigurationResolver(
  855. [],
  856. $config
  857. );
  858. self::assertTrue($resolver->getRiskyAllowed());
  859. }
  860. public function testResolveAllowRiskyWithNegativeConfigAndNoOption(): void
  861. {
  862. $config = new Config();
  863. $config->setRiskyAllowed(false);
  864. $resolver = $this->createConfigurationResolver(
  865. [],
  866. $config
  867. );
  868. self::assertFalse($resolver->getRiskyAllowed());
  869. }
  870. public function testResolveRulesWithConfig(): void
  871. {
  872. $config = new Config();
  873. $config->setRules([
  874. 'statement_indentation' => true,
  875. 'strict_comparison' => false,
  876. ]);
  877. $resolver = $this->createConfigurationResolver(
  878. [],
  879. $config
  880. );
  881. self::assertSameRules(
  882. [
  883. 'statement_indentation' => true,
  884. ],
  885. $resolver->getRules()
  886. );
  887. }
  888. public function testResolveRulesWithOption(): void
  889. {
  890. $resolver = $this->createConfigurationResolver(['rules' => 'statement_indentation,-strict_comparison']);
  891. self::assertSameRules(
  892. [
  893. 'statement_indentation' => true,
  894. ],
  895. $resolver->getRules()
  896. );
  897. }
  898. /**
  899. * @param list<string> $rules
  900. *
  901. * @dataProvider provideResolveRenamedRulesWithUnknownRulesCases
  902. */
  903. public function testResolveRenamedRulesWithUnknownRules(string $expectedMessage, array $rules): void
  904. {
  905. $this->expectException(InvalidConfigurationException::class);
  906. $this->expectExceptionMessage($expectedMessage);
  907. $resolver = $this->createConfigurationResolver(['rules' => implode(',', $rules)]);
  908. $resolver->getRules();
  909. }
  910. public static function provideResolveRenamedRulesWithUnknownRulesCases(): iterable
  911. {
  912. yield 'with config' => [
  913. 'The rules contain unknown fixers: "blank_line_before_return" is renamed (did you mean "blank_line_before_statement"? (note: use configuration "[\'statements\' => [\'return\']]")).
  914. For more info about updating see: https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/blob/v3.0.0/UPGRADE-v3.md#renamed-ruless.',
  915. ['blank_line_before_return'],
  916. ];
  917. yield 'without config' => [
  918. 'The rules contain unknown fixers: "final_static_access" is renamed (did you mean "self_static_accessor"?).
  919. For more info about updating see: https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/blob/v3.0.0/UPGRADE-v3.md#renamed-ruless.',
  920. ['final_static_access'],
  921. ];
  922. yield [
  923. 'The rules contain unknown fixers: "hash_to_slash_comment" is renamed (did you mean "single_line_comment_style"? (note: use configuration "[\'comment_types\' => [\'hash\']]")).
  924. For more info about updating see: https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/blob/v3.0.0/UPGRADE-v3.md#renamed-ruless.',
  925. ['hash_to_slash_comment'],
  926. ];
  927. yield 'both renamed and unknown' => [
  928. 'The rules contain unknown fixers: "final_static_access" is renamed (did you mean "self_static_accessor"?), "binary_operator_space" (did you mean "binary_operator_spaces"?).
  929. For more info about updating see: https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/blob/v3.0.0/UPGRADE-v3.md#renamed-ruless.',
  930. ['final_static_access', 'binary_operator_space'],
  931. ];
  932. }
  933. public function testResolveRulesWithUnknownRules(): void
  934. {
  935. $this->expectException(InvalidConfigurationException::class);
  936. $this->expectExceptionMessage('The rules contain unknown fixers: "bar", "binary_operator_space" (did you mean "binary_operator_spaces"?).');
  937. $resolver = $this->createConfigurationResolver(['rules' => 'statement_indentation,-bar,binary_operator_space']);
  938. $resolver->getRules();
  939. }
  940. public function testResolveRulesWithConfigAndOption(): void
  941. {
  942. $config = new Config();
  943. $config->setRules([
  944. 'statement_indentation' => true,
  945. 'strict_comparison' => false,
  946. ]);
  947. $resolver = $this->createConfigurationResolver(
  948. ['rules' => 'blank_line_before_statement'],
  949. $config
  950. );
  951. self::assertSameRules(
  952. [
  953. 'blank_line_before_statement' => true,
  954. ],
  955. $resolver->getRules()
  956. );
  957. }
  958. public function testResolveCommandLineInputOverridesDefault(): void
  959. {
  960. $command = new FixCommand($this->createToolInfoDouble());
  961. $definition = $command->getDefinition();
  962. $arguments = $definition->getArguments();
  963. self::assertCount(1, $arguments, 'Expected one argument, possibly test needs updating.');
  964. self::assertArrayHasKey('path', $arguments);
  965. $options = $definition->getOptions();
  966. self::assertSame(
  967. ['path-mode', 'allow-risky', 'config', 'dry-run', 'rules', 'using-cache', 'cache-file', 'diff', 'format', 'stop-on-violation', 'show-progress', 'sequential'],
  968. array_keys($options),
  969. 'Expected options mismatch, possibly test needs updating.'
  970. );
  971. $resolver = $this->createConfigurationResolver([
  972. 'path-mode' => 'intersection',
  973. 'allow-risky' => 'yes',
  974. 'config' => null,
  975. 'dry-run' => true,
  976. 'rules' => 'php_unit_construct',
  977. 'using-cache' => 'no',
  978. 'diff' => true,
  979. 'format' => 'json',
  980. 'stop-on-violation' => true,
  981. ]);
  982. self::assertTrue($resolver->shouldStopOnViolation());
  983. self::assertTrue($resolver->getRiskyAllowed());
  984. self::assertTrue($resolver->isDryRun());
  985. self::assertSame(['php_unit_construct' => true], $resolver->getRules());
  986. self::assertFalse($resolver->getUsingCache());
  987. self::assertNull($resolver->getCacheFile());
  988. self::assertInstanceOf(UnifiedDiffer::class, $resolver->getDiffer());
  989. self::assertSame('json', $resolver->getReporter()->getFormat());
  990. self::assertSame('none', $resolver->getProgressType());
  991. }
  992. /**
  993. * @param class-string $expected
  994. * @param null|bool|string $diffConfig
  995. *
  996. * @dataProvider provideResolveDifferCases
  997. */
  998. public function testResolveDiffer(string $expected, $diffConfig): void
  999. {
  1000. $resolver = $this->createConfigurationResolver([
  1001. 'diff' => $diffConfig,
  1002. ]);
  1003. self::assertInstanceOf($expected, $resolver->getDiffer());
  1004. }
  1005. /**
  1006. * @return iterable<array{string, null|bool}>
  1007. */
  1008. public static function provideResolveDifferCases(): iterable
  1009. {
  1010. yield [
  1011. NullDiffer::class,
  1012. false,
  1013. ];
  1014. yield [
  1015. NullDiffer::class,
  1016. null,
  1017. ];
  1018. yield [
  1019. UnifiedDiffer::class,
  1020. true,
  1021. ];
  1022. }
  1023. public function testResolveConfigFileOverridesDefault(): void
  1024. {
  1025. $dir = __DIR__.'/../Fixtures/ConfigurationResolverConfigFile/case_8';
  1026. $resolver = $this->createConfigurationResolver(['path' => [$dir.\DIRECTORY_SEPARATOR.'.php-cs-fixer.php']]);
  1027. self::assertTrue($resolver->getRiskyAllowed());
  1028. self::assertSame(['php_unit_construct' => true], $resolver->getRules());
  1029. self::assertFalse($resolver->getUsingCache());
  1030. self::assertNull($resolver->getCacheFile());
  1031. self::assertSame('xml', $resolver->getReporter()->getFormat());
  1032. self::assertSame('none', $resolver->getProgressType());
  1033. }
  1034. public function testDeprecationOfPassingOtherThanNoOrYes(): void
  1035. {
  1036. $this->expectException(InvalidConfigurationException::class);
  1037. $this->expectExceptionMessage('Expected "yes" or "no" for option "allow-risky", got "yes please".');
  1038. $resolver = $this->createConfigurationResolver(['allow-risky' => 'yes please']);
  1039. $resolver->getRiskyAllowed();
  1040. }
  1041. /**
  1042. * @return iterable<array{bool, bool, null|string}>
  1043. */
  1044. public static function provideResolveBooleanOptionCases(): iterable
  1045. {
  1046. yield [true, true, 'yes'];
  1047. yield [true, false, 'yes'];
  1048. yield [false, true, 'no'];
  1049. yield [false, false, 'no'];
  1050. yield [true, true, null];
  1051. yield [false, false, null];
  1052. }
  1053. public function testWithEmptyRules(): void
  1054. {
  1055. $resolver = $this->createConfigurationResolver(['rules' => '']);
  1056. $this->expectException(InvalidConfigurationException::class);
  1057. $this->expectExceptionMessageMatches('/^Empty rules value is not allowed\.$/');
  1058. $resolver->getRules();
  1059. }
  1060. /**
  1061. * @param array<string, mixed>|bool $ruleConfig
  1062. *
  1063. * @dataProvider provideDeprecatedFixerConfiguredCases
  1064. *
  1065. * @group legacy
  1066. */
  1067. public function testDeprecatedFixerConfigured($ruleConfig): void
  1068. {
  1069. $this->expectDeprecation('Rule "Vendor4/foo" is deprecated. Use "testA" and "testB" instead.');
  1070. $fixer = $this->createDeprecatedFixerDouble();
  1071. $config = new Config();
  1072. $config->registerCustomFixers([$fixer]);
  1073. $config->setRules([$fixer->getName() => $ruleConfig]);
  1074. $resolver = $this->createConfigurationResolver([], $config);
  1075. $resolver->getFixers();
  1076. }
  1077. public static function provideDeprecatedFixerConfiguredCases(): iterable
  1078. {
  1079. yield [true];
  1080. yield [['foo' => true]];
  1081. yield [false];
  1082. }
  1083. /**
  1084. * @dataProvider provideDeprecatedRuleSetConfiguredCases
  1085. *
  1086. * @group legacy
  1087. *
  1088. * @param list<string> $successors
  1089. */
  1090. public function testDeprecatedRuleSetConfigured(string $ruleSet, array $successors): void
  1091. {
  1092. $this->expectDeprecation(\sprintf(
  1093. 'Rule set "%s" is deprecated. %s.',
  1094. $ruleSet,
  1095. [] === $successors
  1096. ? 'No replacement available'
  1097. : \sprintf('Use %s instead', Utils::naturalLanguageJoin($successors))
  1098. ));
  1099. $config = new Config();
  1100. $config->setRules([$ruleSet => true]);
  1101. $config->setRiskyAllowed(true);
  1102. $resolver = $this->createConfigurationResolver([], $config);
  1103. $resolver->getFixers();
  1104. }
  1105. /**
  1106. * @return iterable<array{0: string, 1: list<string>}>
  1107. */
  1108. public static function provideDeprecatedRuleSetConfiguredCases(): iterable
  1109. {
  1110. yield ['@PER', ['@PER-CS']];
  1111. yield ['@PER:risky', ['@PER-CS:risky']];
  1112. }
  1113. /**
  1114. * @return iterable<array{null|string, string, string}>
  1115. */
  1116. public static function provideGetDirectoryCases(): iterable
  1117. {
  1118. yield [null, '/my/path/my/file', 'my/file'];
  1119. yield ['/my/path/.php-cs-fixer.cache', '/my/path/my/file', 'my/file'];
  1120. yield ['/my/path2/dir/.php-cs-fixer.cache', '/my/path2/dir/dir2/file', 'dir2/file'];
  1121. yield ['dir/.php-cs-fixer.cache', '/my/path/dir/dir3/file', 'dir3/file'];
  1122. }
  1123. /**
  1124. * @dataProvider provideGetDirectoryCases
  1125. */
  1126. public function testGetDirectory(?string $cacheFile, string $file, string $expectedPathRelativeToFile): void
  1127. {
  1128. if (null !== $cacheFile) {
  1129. $cacheFile = $this->normalizePath($cacheFile);
  1130. }
  1131. $file = $this->normalizePath($file);
  1132. $expectedPathRelativeToFile = $this->normalizePath($expectedPathRelativeToFile);
  1133. $config = new Config();
  1134. if (null === $cacheFile) {
  1135. $config->setUsingCache(false);
  1136. } else {
  1137. $config->setCacheFile($cacheFile);
  1138. }
  1139. $resolver = new ConfigurationResolver($config, [], $this->normalizePath('/my/path'), $this->createToolInfoDouble());
  1140. $directory = $resolver->getDirectory();
  1141. self::assertSame($expectedPathRelativeToFile, $directory->getRelativePathTo($file));
  1142. }
  1143. private function normalizePath(string $path): string
  1144. {
  1145. return str_replace('/', \DIRECTORY_SEPARATOR, $path);
  1146. }
  1147. /**
  1148. * @param array<string, array<string, mixed>|bool> $expected
  1149. * @param array<string, array<string, mixed>|bool> $actual
  1150. */
  1151. private static function assertSameRules(array $expected, array $actual): void
  1152. {
  1153. ksort($expected);
  1154. ksort($actual);
  1155. self::assertSame($expected, $actual);
  1156. }
  1157. private static function getFixtureDir(): string
  1158. {
  1159. return realpath(__DIR__.\DIRECTORY_SEPARATOR.'..'.\DIRECTORY_SEPARATOR.'Fixtures'.\DIRECTORY_SEPARATOR.'ConfigurationResolverConfigFile'.\DIRECTORY_SEPARATOR).'/';
  1160. }
  1161. /**
  1162. * @param array<string, mixed> $options
  1163. */
  1164. private function createConfigurationResolver(
  1165. array $options,
  1166. ?ConfigInterface $config = null,
  1167. string $cwdPath = '',
  1168. ?ToolInfoInterface $toolInfo = null
  1169. ): ConfigurationResolver {
  1170. return new ConfigurationResolver(
  1171. $config ?? new Config(),
  1172. $options,
  1173. $cwdPath,
  1174. $toolInfo ?? $this->createToolInfoDouble()
  1175. );
  1176. }
  1177. private function createDeprecatedFixerDouble(): DeprecatedFixerInterface
  1178. {
  1179. return new class extends AbstractFixer implements DeprecatedFixerInterface, ConfigurableFixerInterface {
  1180. /** @use ConfigurableFixerTrait<array<string, mixed>, array<string, mixed>> */
  1181. use ConfigurableFixerTrait;
  1182. public function getDefinition(): FixerDefinitionInterface
  1183. {
  1184. throw new \LogicException('Not implemented.');
  1185. }
  1186. public function isCandidate(Tokens $tokens): bool
  1187. {
  1188. throw new \LogicException('Not implemented.');
  1189. }
  1190. public function getSuccessorsNames(): array
  1191. {
  1192. return ['testA', 'testB'];
  1193. }
  1194. public function getName(): string
  1195. {
  1196. return 'Vendor4/foo';
  1197. }
  1198. protected function applyFix(\SplFileInfo $file, Tokens $tokens): void {}
  1199. protected function createConfigurationDefinition(): FixerConfigurationResolverInterface
  1200. {
  1201. return new FixerConfigurationResolver([
  1202. (new FixerOptionBuilder('foo', 'Foo.'))->getOption(),
  1203. ]);
  1204. }
  1205. };
  1206. }
  1207. private function createToolInfoDouble(): ToolInfoInterface
  1208. {
  1209. return new class implements ToolInfoInterface {
  1210. public function getComposerInstallationDetails(): array
  1211. {
  1212. throw new \BadMethodCallException();
  1213. }
  1214. public function getComposerVersion(): string
  1215. {
  1216. throw new \BadMethodCallException();
  1217. }
  1218. public function getVersion(): string
  1219. {
  1220. throw new \BadMethodCallException();
  1221. }
  1222. public function isInstalledAsPhar(): bool
  1223. {
  1224. return true;
  1225. }
  1226. public function isInstalledByComposer(): bool
  1227. {
  1228. throw new \BadMethodCallException();
  1229. }
  1230. public function isRunInsideDocker(): bool
  1231. {
  1232. return false;
  1233. }
  1234. public function getPharDownloadUri(string $version): string
  1235. {
  1236. throw new \BadMethodCallException();
  1237. }
  1238. };
  1239. }
  1240. }