ConfigurationResolver.php 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957
  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\Console;
  12. use PhpCsFixer\Cache\CacheManagerInterface;
  13. use PhpCsFixer\Cache\Directory;
  14. use PhpCsFixer\Cache\DirectoryInterface;
  15. use PhpCsFixer\Cache\FileCacheManager;
  16. use PhpCsFixer\Cache\FileHandler;
  17. use PhpCsFixer\Cache\NullCacheManager;
  18. use PhpCsFixer\Cache\Signature;
  19. use PhpCsFixer\ConfigInterface;
  20. use PhpCsFixer\ConfigurationException\InvalidConfigurationException;
  21. use PhpCsFixer\Console\Report\FixReport\ReporterFactory;
  22. use PhpCsFixer\Console\Report\FixReport\ReporterInterface;
  23. use PhpCsFixer\Differ\DifferInterface;
  24. use PhpCsFixer\Differ\NullDiffer;
  25. use PhpCsFixer\Differ\SebastianBergmannDiffer;
  26. use PhpCsFixer\Differ\UnifiedDiffer;
  27. use PhpCsFixer\Finder;
  28. use PhpCsFixer\Fixer\DeprecatedFixerInterface;
  29. use PhpCsFixer\Fixer\FixerInterface;
  30. use PhpCsFixer\FixerFactory;
  31. use PhpCsFixer\Linter\Linter;
  32. use PhpCsFixer\Linter\LinterInterface;
  33. use PhpCsFixer\RuleSet\RuleSet;
  34. use PhpCsFixer\StdinFileInfo;
  35. use PhpCsFixer\ToolInfoInterface;
  36. use PhpCsFixer\Utils;
  37. use PhpCsFixer\WhitespacesFixerConfig;
  38. use PhpCsFixer\WordMatcher;
  39. use Symfony\Component\Console\Output\OutputInterface;
  40. use Symfony\Component\Filesystem\Filesystem;
  41. use Symfony\Component\Finder\Finder as SymfonyFinder;
  42. /**
  43. * The resolver that resolves configuration to use by command line options and config.
  44. *
  45. * @author Fabien Potencier <fabien@symfony.com>
  46. * @author Katsuhiro Ogawa <ko.fivestar@gmail.com>
  47. * @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
  48. *
  49. * @internal
  50. */
  51. final class ConfigurationResolver
  52. {
  53. const PATH_MODE_OVERRIDE = 'override';
  54. const PATH_MODE_INTERSECTION = 'intersection';
  55. /**
  56. * @var null|bool
  57. */
  58. private $allowRisky;
  59. /**
  60. * @var null|ConfigInterface
  61. */
  62. private $config;
  63. /**
  64. * @var null|string
  65. */
  66. private $configFile;
  67. /**
  68. * @var string
  69. */
  70. private $cwd;
  71. /**
  72. * @var ConfigInterface
  73. */
  74. private $defaultConfig;
  75. /**
  76. * @var null|ReporterInterface
  77. */
  78. private $reporter;
  79. /**
  80. * @var null|bool
  81. */
  82. private $isStdIn;
  83. /**
  84. * @var null|bool
  85. */
  86. private $isDryRun;
  87. /**
  88. * @var null|FixerInterface[]
  89. */
  90. private $fixers;
  91. /**
  92. * @var null|bool
  93. */
  94. private $configFinderIsOverridden;
  95. /**
  96. * @var ToolInfoInterface
  97. */
  98. private $toolInfo;
  99. /**
  100. * @var array
  101. */
  102. private $options = [
  103. 'allow-risky' => null,
  104. 'cache-file' => null,
  105. 'config' => null,
  106. 'diff' => null,
  107. 'diff-format' => null,
  108. 'dry-run' => null,
  109. 'format' => null,
  110. 'path' => [],
  111. 'path-mode' => self::PATH_MODE_OVERRIDE,
  112. 'rules' => null,
  113. 'show-progress' => null,
  114. 'stop-on-violation' => null,
  115. 'using-cache' => null,
  116. 'verbosity' => null,
  117. ];
  118. private $cacheFile;
  119. private $cacheManager;
  120. private $differ;
  121. private $directory;
  122. private $finder;
  123. private $format;
  124. private $linter;
  125. private $path;
  126. private $progress;
  127. private $ruleSet;
  128. private $usingCache;
  129. /**
  130. * @var FixerFactory
  131. */
  132. private $fixerFactory;
  133. /**
  134. * @param string $cwd
  135. */
  136. public function __construct(
  137. ConfigInterface $config,
  138. array $options,
  139. $cwd,
  140. ToolInfoInterface $toolInfo
  141. ) {
  142. $this->cwd = $cwd;
  143. $this->defaultConfig = $config;
  144. $this->toolInfo = $toolInfo;
  145. foreach ($options as $name => $value) {
  146. $this->setOption($name, $value);
  147. }
  148. }
  149. /**
  150. * @return null|string
  151. */
  152. public function getCacheFile()
  153. {
  154. if (!$this->getUsingCache()) {
  155. return null;
  156. }
  157. if (null === $this->cacheFile) {
  158. if (null === $this->options['cache-file']) {
  159. $this->cacheFile = $this->getConfig()->getCacheFile();
  160. } else {
  161. $this->cacheFile = $this->options['cache-file'];
  162. }
  163. }
  164. return $this->cacheFile;
  165. }
  166. /**
  167. * @return CacheManagerInterface
  168. */
  169. public function getCacheManager()
  170. {
  171. if (null === $this->cacheManager) {
  172. $cacheFile = $this->getCacheFile();
  173. if (null === $cacheFile) {
  174. $this->cacheManager = new NullCacheManager();
  175. } else {
  176. $this->cacheManager = new FileCacheManager(
  177. new FileHandler($cacheFile),
  178. new Signature(
  179. PHP_VERSION,
  180. $this->toolInfo->getVersion(),
  181. $this->getConfig()->getIndent(),
  182. $this->getConfig()->getLineEnding(),
  183. $this->getRules()
  184. ),
  185. $this->isDryRun(),
  186. $this->getDirectory()
  187. );
  188. }
  189. }
  190. return $this->cacheManager;
  191. }
  192. /**
  193. * @return ConfigInterface
  194. */
  195. public function getConfig()
  196. {
  197. if (null === $this->config) {
  198. foreach ($this->computeConfigFiles() as $configFile) {
  199. if (!file_exists($configFile)) {
  200. continue;
  201. }
  202. $configFileBasename = basename($configFile);
  203. $deprecatedConfigs = [
  204. '.php_cs' => '.php-cs-fixer.php',
  205. '.php_cs.dist' => '.php-cs-fixer.dist.php',
  206. ];
  207. if (isset($deprecatedConfigs[$configFileBasename])) {
  208. $message = "Configuration file `{$configFileBasename}` is deprecated, rename to `{$deprecatedConfigs[$configFileBasename]}`.";
  209. Utils::triggerDeprecation(new InvalidConfigurationException($message));
  210. }
  211. $config = self::separatedContextLessInclude($configFile);
  212. // verify that the config has an instance of Config
  213. if (!$config instanceof ConfigInterface) {
  214. throw new InvalidConfigurationException(sprintf('The config file: "%s" does not return a "PhpCsFixer\ConfigInterface" instance. Got: "%s".', $configFile, \is_object($config) ? \get_class($config) : \gettype($config)));
  215. }
  216. $this->config = $config;
  217. $this->configFile = $configFile;
  218. break;
  219. }
  220. if (null === $this->config) {
  221. $this->config = $this->defaultConfig;
  222. }
  223. }
  224. return $this->config;
  225. }
  226. /**
  227. * @return null|string
  228. */
  229. public function getConfigFile()
  230. {
  231. if (null === $this->configFile) {
  232. $this->getConfig();
  233. }
  234. return $this->configFile;
  235. }
  236. /**
  237. * @return DifferInterface
  238. */
  239. public function getDiffer()
  240. {
  241. if (null === $this->differ) {
  242. $mapper = [
  243. 'null' => static function () { return new NullDiffer(); },
  244. 'sbd' => static function () { return new SebastianBergmannDiffer(); },
  245. 'udiff' => static function () { return new UnifiedDiffer(); },
  246. ];
  247. if (!$this->options['diff']) {
  248. $defaultOption = 'null';
  249. } elseif (getenv('PHP_CS_FIXER_FUTURE_MODE')) {
  250. $defaultOption = 'udiff';
  251. } else {
  252. $defaultOption = 'sbd'; // @TODO: 3.0 change to udiff as default
  253. }
  254. $option = isset($this->options['diff-format'])
  255. ? $this->options['diff-format']
  256. : $defaultOption;
  257. if (!\is_string($option)) {
  258. throw new InvalidConfigurationException(sprintf(
  259. '"diff-format" must be a string, "%s" given.',
  260. \gettype($option)
  261. ));
  262. }
  263. if (is_subclass_of($option, DifferInterface::class)) {
  264. $this->differ = new $option();
  265. } elseif (!isset($mapper[$option])) {
  266. throw new InvalidConfigurationException(sprintf(
  267. '"diff-format" must be any of "%s", got "%s".',
  268. implode('", "', array_keys($mapper)),
  269. $option
  270. ));
  271. } else {
  272. $this->differ = $mapper[$option]();
  273. }
  274. }
  275. return $this->differ;
  276. }
  277. /**
  278. * @return DirectoryInterface
  279. */
  280. public function getDirectory()
  281. {
  282. if (null === $this->directory) {
  283. $path = $this->getCacheFile();
  284. if (null === $path) {
  285. $absolutePath = $this->cwd;
  286. } else {
  287. $filesystem = new Filesystem();
  288. $absolutePath = $filesystem->isAbsolutePath($path)
  289. ? $path
  290. : $this->cwd.\DIRECTORY_SEPARATOR.$path;
  291. }
  292. $this->directory = new Directory(\dirname($absolutePath));
  293. }
  294. return $this->directory;
  295. }
  296. /**
  297. * @return FixerInterface[] An array of FixerInterface
  298. */
  299. public function getFixers()
  300. {
  301. if (null === $this->fixers) {
  302. $this->fixers = $this->createFixerFactory()
  303. ->useRuleSet($this->getRuleSet())
  304. ->setWhitespacesConfig(new WhitespacesFixerConfig($this->config->getIndent(), $this->config->getLineEnding()))
  305. ->getFixers()
  306. ;
  307. if (false === $this->getRiskyAllowed()) {
  308. $riskyFixers = array_map(
  309. static function (FixerInterface $fixer) {
  310. return $fixer->getName();
  311. },
  312. array_filter(
  313. $this->fixers,
  314. static function (FixerInterface $fixer) {
  315. return $fixer->isRisky();
  316. }
  317. )
  318. );
  319. if (\count($riskyFixers)) {
  320. throw new InvalidConfigurationException(sprintf('The rules contain risky fixers ("%s"), but they are not allowed to run. Perhaps you forget to use --allow-risky=yes option?', implode('", "', $riskyFixers)));
  321. }
  322. }
  323. }
  324. return $this->fixers;
  325. }
  326. /**
  327. * @return LinterInterface
  328. */
  329. public function getLinter()
  330. {
  331. if (null === $this->linter) {
  332. $this->linter = new Linter($this->getConfig()->getPhpExecutable());
  333. }
  334. return $this->linter;
  335. }
  336. /**
  337. * Returns path.
  338. *
  339. * @return string[]
  340. */
  341. public function getPath()
  342. {
  343. if (null === $this->path) {
  344. $filesystem = new Filesystem();
  345. $cwd = $this->cwd;
  346. if (1 === \count($this->options['path']) && '-' === $this->options['path'][0]) {
  347. $this->path = $this->options['path'];
  348. } else {
  349. $this->path = array_map(
  350. static function ($rawPath) use ($cwd, $filesystem) {
  351. $path = trim($rawPath);
  352. if ('' === $path) {
  353. throw new InvalidConfigurationException("Invalid path: \"{$rawPath}\".");
  354. }
  355. $absolutePath = $filesystem->isAbsolutePath($path)
  356. ? $path
  357. : $cwd.\DIRECTORY_SEPARATOR.$path;
  358. if (!file_exists($absolutePath)) {
  359. throw new InvalidConfigurationException(sprintf(
  360. 'The path "%s" is not readable.',
  361. $path
  362. ));
  363. }
  364. return $absolutePath;
  365. },
  366. $this->options['path']
  367. );
  368. }
  369. }
  370. return $this->path;
  371. }
  372. /**
  373. * @throws InvalidConfigurationException
  374. *
  375. * @return string
  376. */
  377. public function getProgress()
  378. {
  379. if (null === $this->progress) {
  380. if (OutputInterface::VERBOSITY_VERBOSE <= $this->options['verbosity'] && 'txt' === $this->getFormat()) {
  381. $progressType = $this->options['show-progress'];
  382. $progressTypes = ['none', 'run-in', 'estimating', 'estimating-max', 'dots'];
  383. if (null === $progressType) {
  384. $default = 'run-in';
  385. if (getenv('PHP_CS_FIXER_FUTURE_MODE')) {
  386. $default = 'dots';
  387. }
  388. $progressType = $this->getConfig()->getHideProgress() ? 'none' : $default;
  389. } elseif (!\in_array($progressType, $progressTypes, true)) {
  390. throw new InvalidConfigurationException(sprintf(
  391. 'The progress type "%s" is not defined, supported are "%s".',
  392. $progressType,
  393. implode('", "', $progressTypes)
  394. ));
  395. } elseif (\in_array($progressType, ['estimating', 'estimating-max', 'run-in'], true)) {
  396. Utils::triggerDeprecation(new \InvalidArgumentException(
  397. 'Passing `estimating`, `estimating-max` or `run-in` is deprecated and will not be supported in 3.0, use `none` or `dots` instead.'
  398. ));
  399. }
  400. $this->progress = $progressType;
  401. } else {
  402. $this->progress = 'none';
  403. }
  404. }
  405. return $this->progress;
  406. }
  407. /**
  408. * @return ReporterInterface
  409. */
  410. public function getReporter()
  411. {
  412. if (null === $this->reporter) {
  413. $reporterFactory = new ReporterFactory();
  414. $reporterFactory->registerBuiltInReporters();
  415. $format = $this->getFormat();
  416. try {
  417. $this->reporter = $reporterFactory->getReporter($format);
  418. } catch (\UnexpectedValueException $e) {
  419. $formats = $reporterFactory->getFormats();
  420. sort($formats);
  421. throw new InvalidConfigurationException(sprintf('The format "%s" is not defined, supported are "%s".', $format, implode('", "', $formats)));
  422. }
  423. }
  424. return $this->reporter;
  425. }
  426. /**
  427. * @return bool
  428. */
  429. public function getRiskyAllowed()
  430. {
  431. if (null === $this->allowRisky) {
  432. if (null === $this->options['allow-risky']) {
  433. $this->allowRisky = $this->getConfig()->getRiskyAllowed();
  434. } else {
  435. $this->allowRisky = $this->resolveOptionBooleanValue('allow-risky');
  436. }
  437. }
  438. return $this->allowRisky;
  439. }
  440. /**
  441. * Returns rules.
  442. *
  443. * @return array
  444. */
  445. public function getRules()
  446. {
  447. return $this->getRuleSet()->getRules();
  448. }
  449. /**
  450. * @return bool
  451. */
  452. public function getUsingCache()
  453. {
  454. if (null === $this->usingCache) {
  455. if (null === $this->options['using-cache']) {
  456. $this->usingCache = $this->getConfig()->getUsingCache();
  457. } else {
  458. $this->usingCache = $this->resolveOptionBooleanValue('using-cache');
  459. }
  460. }
  461. $this->usingCache = $this->usingCache && ($this->toolInfo->isInstalledAsPhar() || $this->toolInfo->isInstalledByComposer());
  462. return $this->usingCache;
  463. }
  464. public function getFinder()
  465. {
  466. if (null === $this->finder) {
  467. $this->finder = $this->resolveFinder();
  468. }
  469. return $this->finder;
  470. }
  471. /**
  472. * Returns dry-run flag.
  473. *
  474. * @return bool
  475. */
  476. public function isDryRun()
  477. {
  478. if (null === $this->isDryRun) {
  479. if ($this->isStdIn()) {
  480. // Can't write to STDIN
  481. $this->isDryRun = true;
  482. } else {
  483. $this->isDryRun = $this->options['dry-run'];
  484. }
  485. }
  486. return $this->isDryRun;
  487. }
  488. public function shouldStopOnViolation()
  489. {
  490. return $this->options['stop-on-violation'];
  491. }
  492. /**
  493. * @return bool
  494. */
  495. public function configFinderIsOverridden()
  496. {
  497. if (null === $this->configFinderIsOverridden) {
  498. $this->resolveFinder();
  499. }
  500. return $this->configFinderIsOverridden;
  501. }
  502. /**
  503. * Compute file candidates for config file.
  504. *
  505. * @return string[]
  506. */
  507. private function computeConfigFiles()
  508. {
  509. $configFile = $this->options['config'];
  510. if (null !== $configFile) {
  511. if (false === file_exists($configFile) || false === is_readable($configFile)) {
  512. throw new InvalidConfigurationException(sprintf('Cannot read config file "%s".', $configFile));
  513. }
  514. return [$configFile];
  515. }
  516. $path = $this->getPath();
  517. if ($this->isStdIn() || 0 === \count($path)) {
  518. $configDir = $this->cwd;
  519. } elseif (1 < \count($path)) {
  520. throw new InvalidConfigurationException('For multiple paths config parameter is required.');
  521. } elseif (!is_file($path[0])) {
  522. $configDir = $path[0];
  523. } else {
  524. $dirName = pathinfo($path[0], PATHINFO_DIRNAME);
  525. $configDir = $dirName ?: $path[0];
  526. }
  527. $candidates = [
  528. $configDir.\DIRECTORY_SEPARATOR.'.php-cs-fixer.php',
  529. $configDir.\DIRECTORY_SEPARATOR.'.php-cs-fixer.dist.php',
  530. $configDir.\DIRECTORY_SEPARATOR.'.php_cs', // old v2 config, present here only to throw nice error message later
  531. $configDir.\DIRECTORY_SEPARATOR.'.php_cs.dist', // old v2 config, present here only to throw nice error message later
  532. ];
  533. if ($configDir !== $this->cwd) {
  534. $candidates[] = $this->cwd.\DIRECTORY_SEPARATOR.'.php-cs-fixer.php';
  535. $candidates[] = $this->cwd.\DIRECTORY_SEPARATOR.'.php-cs-fixer.dist.php';
  536. $candidates[] = $this->cwd.\DIRECTORY_SEPARATOR.'.php_cs'; // old v2 config, present here only to throw nice error message later
  537. $candidates[] = $this->cwd.\DIRECTORY_SEPARATOR.'.php_cs.dist'; // old v2 config, present here only to throw nice error message later
  538. }
  539. return $candidates;
  540. }
  541. /**
  542. * @return FixerFactory
  543. */
  544. private function createFixerFactory()
  545. {
  546. if (null === $this->fixerFactory) {
  547. $fixerFactory = new FixerFactory();
  548. $fixerFactory->registerBuiltInFixers();
  549. $fixerFactory->registerCustomFixers($this->getConfig()->getCustomFixers());
  550. $this->fixerFactory = $fixerFactory;
  551. }
  552. return $this->fixerFactory;
  553. }
  554. /**
  555. * @return string
  556. */
  557. private function getFormat()
  558. {
  559. if (null === $this->format) {
  560. $this->format = null === $this->options['format']
  561. ? $this->getConfig()->getFormat()
  562. : $this->options['format'];
  563. }
  564. return $this->format;
  565. }
  566. private function getRuleSet()
  567. {
  568. if (null === $this->ruleSet) {
  569. $rules = $this->parseRules();
  570. $this->validateRules($rules);
  571. $this->ruleSet = new RuleSet($rules);
  572. }
  573. return $this->ruleSet;
  574. }
  575. /**
  576. * @return bool
  577. */
  578. private function isStdIn()
  579. {
  580. if (null === $this->isStdIn) {
  581. $this->isStdIn = 1 === \count($this->options['path']) && '-' === $this->options['path'][0];
  582. }
  583. return $this->isStdIn;
  584. }
  585. /**
  586. * @param iterable $iterable
  587. *
  588. * @return \Traversable
  589. */
  590. private function iterableToTraversable($iterable)
  591. {
  592. return \is_array($iterable) ? new \ArrayIterator($iterable) : $iterable;
  593. }
  594. /**
  595. * Compute rules.
  596. *
  597. * @return array
  598. */
  599. private function parseRules()
  600. {
  601. if (null === $this->options['rules']) {
  602. return $this->getConfig()->getRules();
  603. }
  604. $rules = trim($this->options['rules']);
  605. if ('' === $rules) {
  606. throw new InvalidConfigurationException('Empty rules value is not allowed.');
  607. }
  608. if ('{' === $rules[0]) {
  609. $rules = json_decode($rules, true);
  610. if (JSON_ERROR_NONE !== json_last_error()) {
  611. throw new InvalidConfigurationException(sprintf('Invalid JSON rules input: "%s".', json_last_error_msg()));
  612. }
  613. return $rules;
  614. }
  615. $rules = [];
  616. foreach (explode(',', $this->options['rules']) as $rule) {
  617. $rule = trim($rule);
  618. if ('' === $rule) {
  619. throw new InvalidConfigurationException('Empty rule name is not allowed.');
  620. }
  621. if ('-' === $rule[0]) {
  622. $rules[substr($rule, 1)] = false;
  623. } else {
  624. $rules[$rule] = true;
  625. }
  626. }
  627. return $rules;
  628. }
  629. /**
  630. * @throws InvalidConfigurationException
  631. */
  632. private function validateRules(array $rules)
  633. {
  634. /**
  635. * Create a ruleset that contains all configured rules, even when they originally have been disabled.
  636. *
  637. * @see RuleSet::resolveSet()
  638. */
  639. $ruleSet = [];
  640. foreach ($rules as $key => $value) {
  641. if (\is_int($key)) {
  642. throw new InvalidConfigurationException(sprintf('Missing value for "%s" rule/set.', $value));
  643. }
  644. $ruleSet[$key] = true;
  645. }
  646. $ruleSet = new RuleSet($ruleSet);
  647. /** @var string[] $configuredFixers */
  648. $configuredFixers = array_keys($ruleSet->getRules());
  649. $fixers = $this->createFixerFactory()->getFixers();
  650. /** @var string[] $availableFixers */
  651. $availableFixers = array_map(static function (FixerInterface $fixer) {
  652. return $fixer->getName();
  653. }, $fixers);
  654. $unknownFixers = array_diff(
  655. $configuredFixers,
  656. $availableFixers
  657. );
  658. if (\count($unknownFixers)) {
  659. $matcher = new WordMatcher($availableFixers);
  660. $message = 'The rules contain unknown fixers: ';
  661. foreach ($unknownFixers as $unknownFixer) {
  662. $alternative = $matcher->match($unknownFixer);
  663. $message .= sprintf(
  664. '"%s"%s, ',
  665. $unknownFixer,
  666. null === $alternative ? '' : ' (did you mean "'.$alternative.'"?)'
  667. );
  668. }
  669. throw new InvalidConfigurationException(substr($message, 0, -2).'.');
  670. }
  671. foreach ($fixers as $fixer) {
  672. $fixerName = $fixer->getName();
  673. if (isset($rules[$fixerName]) && $fixer instanceof DeprecatedFixerInterface) {
  674. $successors = $fixer->getSuccessorsNames();
  675. $messageEnd = [] === $successors
  676. ? sprintf(' and will be removed in version %d.0.', Application::getMajorVersion() + 1)
  677. : sprintf('. Use %s instead.', str_replace('`', '"', Utils::naturalLanguageJoinWithBackticks($successors)));
  678. Utils::triggerDeprecation(new \RuntimeException("Rule \"{$fixerName}\" is deprecated{$messageEnd}"));
  679. }
  680. }
  681. }
  682. /**
  683. * Apply path on config instance.
  684. */
  685. private function resolveFinder()
  686. {
  687. $this->configFinderIsOverridden = false;
  688. if ($this->isStdIn()) {
  689. return new \ArrayIterator([new StdinFileInfo()]);
  690. }
  691. $modes = [self::PATH_MODE_OVERRIDE, self::PATH_MODE_INTERSECTION];
  692. if (!\in_array(
  693. $this->options['path-mode'],
  694. $modes,
  695. true
  696. )) {
  697. throw new InvalidConfigurationException(sprintf(
  698. 'The path-mode "%s" is not defined, supported are "%s".',
  699. $this->options['path-mode'],
  700. implode('", "', $modes)
  701. ));
  702. }
  703. $isIntersectionPathMode = self::PATH_MODE_INTERSECTION === $this->options['path-mode'];
  704. $paths = array_filter(array_map(
  705. static function ($path) {
  706. return realpath($path);
  707. },
  708. $this->getPath()
  709. ));
  710. if (!\count($paths)) {
  711. if ($isIntersectionPathMode) {
  712. return new \ArrayIterator([]);
  713. }
  714. return $this->iterableToTraversable($this->getConfig()->getFinder());
  715. }
  716. $pathsByType = [
  717. 'file' => [],
  718. 'dir' => [],
  719. ];
  720. foreach ($paths as $path) {
  721. if (is_file($path)) {
  722. $pathsByType['file'][] = $path;
  723. } else {
  724. $pathsByType['dir'][] = $path.\DIRECTORY_SEPARATOR;
  725. }
  726. }
  727. $nestedFinder = null;
  728. $currentFinder = $this->iterableToTraversable($this->getConfig()->getFinder());
  729. try {
  730. $nestedFinder = $currentFinder instanceof \IteratorAggregate ? $currentFinder->getIterator() : $currentFinder;
  731. } catch (\Exception $e) {
  732. }
  733. if ($isIntersectionPathMode) {
  734. if (null === $nestedFinder) {
  735. throw new InvalidConfigurationException(
  736. 'Cannot create intersection with not-fully defined Finder in configuration file.'
  737. );
  738. }
  739. return new \CallbackFilterIterator(
  740. new \IteratorIterator($nestedFinder),
  741. static function (\SplFileInfo $current) use ($pathsByType) {
  742. $currentRealPath = $current->getRealPath();
  743. if (\in_array($currentRealPath, $pathsByType['file'], true)) {
  744. return true;
  745. }
  746. foreach ($pathsByType['dir'] as $path) {
  747. if (0 === strpos($currentRealPath, $path)) {
  748. return true;
  749. }
  750. }
  751. return false;
  752. }
  753. );
  754. }
  755. if (null !== $this->getConfigFile() && null !== $nestedFinder) {
  756. $this->configFinderIsOverridden = true;
  757. }
  758. if ($currentFinder instanceof SymfonyFinder && null === $nestedFinder) {
  759. // finder from configuration Symfony finder and it is not fully defined, we may fulfill it
  760. return $currentFinder->in($pathsByType['dir'])->append($pathsByType['file']);
  761. }
  762. return Finder::create()->in($pathsByType['dir'])->append($pathsByType['file']);
  763. }
  764. /**
  765. * Set option that will be resolved.
  766. *
  767. * @param string $name
  768. * @param mixed $value
  769. */
  770. private function setOption($name, $value)
  771. {
  772. if (!\array_key_exists($name, $this->options)) {
  773. throw new InvalidConfigurationException(sprintf('Unknown option name: "%s".', $name));
  774. }
  775. $this->options[$name] = $value;
  776. }
  777. /**
  778. * @param string $optionName
  779. *
  780. * @return bool
  781. */
  782. private function resolveOptionBooleanValue($optionName)
  783. {
  784. $value = $this->options[$optionName];
  785. if (\is_bool($value)) {
  786. return $value;
  787. }
  788. if (!\is_string($value)) {
  789. throw new InvalidConfigurationException(sprintf('Expected boolean or string value for option "%s".', $optionName));
  790. }
  791. if ('yes' === $value) {
  792. return true;
  793. }
  794. if ('no' === $value) {
  795. return false;
  796. }
  797. Utils::triggerDeprecation(new InvalidConfigurationException(
  798. sprintf('Expected "yes" or "no" for option "%s", other values are deprecated and support will be removed in 3.0. Got "%s", this implicitly set the option to "false".', $optionName, $value)
  799. ));
  800. return false;
  801. }
  802. private static function separatedContextLessInclude($path)
  803. {
  804. return include $path;
  805. }
  806. }