ConfigurationResolverTest.php 38 KB

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