Runner.php 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829
  1. <?php
  2. /**
  3. * Responsible for running PHPCS and PHPCBF.
  4. *
  5. * After creating an object of this class, you probably just want to
  6. * call runPHPCS() or runPHPCBF().
  7. *
  8. * @author Greg Sherwood <gsherwood@squiz.net>
  9. * @copyright 2006-2015 Squiz Pty Ltd (ABN 77 084 670 600)
  10. * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
  11. */
  12. namespace PHP_CodeSniffer;
  13. use PHP_CodeSniffer\Files\FileList;
  14. use PHP_CodeSniffer\Files\File;
  15. use PHP_CodeSniffer\Files\DummyFile;
  16. use PHP_CodeSniffer\Util\Cache;
  17. use PHP_CodeSniffer\Util\Common;
  18. use PHP_CodeSniffer\Util\Standards;
  19. use PHP_CodeSniffer\Exceptions\RuntimeException;
  20. use PHP_CodeSniffer\Exceptions\DeepExitException;
  21. class Runner
  22. {
  23. /**
  24. * The config data for the run.
  25. *
  26. * @var \PHP_CodeSniffer\Config
  27. */
  28. public $config = null;
  29. /**
  30. * The ruleset used for the run.
  31. *
  32. * @var \PHP_CodeSniffer\Ruleset
  33. */
  34. public $ruleset = null;
  35. /**
  36. * The reporter used for generating reports after the run.
  37. *
  38. * @var \PHP_CodeSniffer\Reporter
  39. */
  40. public $reporter = null;
  41. /**
  42. * Run the PHPCS script.
  43. *
  44. * @return array
  45. */
  46. public function runPHPCS()
  47. {
  48. try {
  49. Util\Timing::startTiming();
  50. Runner::checkRequirements();
  51. if (defined('PHP_CODESNIFFER_CBF') === false) {
  52. define('PHP_CODESNIFFER_CBF', false);
  53. }
  54. // Creating the Config object populates it with all required settings
  55. // based on the CLI arguments provided to the script and any config
  56. // values the user has set.
  57. $this->config = new Config();
  58. // Init the run and load the rulesets to set additional config vars.
  59. $this->init();
  60. // Print a list of sniffs in each of the supplied standards.
  61. // We fudge the config here so that each standard is explained in isolation.
  62. if ($this->config->explain === true) {
  63. $standards = $this->config->standards;
  64. foreach ($standards as $standard) {
  65. $this->config->standards = [$standard];
  66. $ruleset = new Ruleset($this->config);
  67. $ruleset->explain();
  68. }
  69. return 0;
  70. }
  71. // Generate documentation for each of the supplied standards.
  72. if ($this->config->generator !== null) {
  73. $standards = $this->config->standards;
  74. foreach ($standards as $standard) {
  75. $this->config->standards = [$standard];
  76. $ruleset = new Ruleset($this->config);
  77. $class = 'PHP_CodeSniffer\Generators\\'.$this->config->generator;
  78. $generator = new $class($ruleset);
  79. $generator->generate();
  80. }
  81. return 0;
  82. }
  83. // Other report formats don't really make sense in interactive mode
  84. // so we hard-code the full report here and when outputting.
  85. // We also ensure parallel processing is off because we need to do one file at a time.
  86. if ($this->config->interactive === true) {
  87. $this->config->reports = ['full' => null];
  88. $this->config->parallel = 1;
  89. $this->config->showProgress = false;
  90. }
  91. // Disable caching if we are processing STDIN as we can't be 100%
  92. // sure where the file came from or if it will change in the future.
  93. if ($this->config->stdin === true) {
  94. $this->config->cache = false;
  95. }
  96. $numErrors = $this->run();
  97. // Print all the reports for this run.
  98. $toScreen = $this->reporter->printReports();
  99. // Only print timer output if no reports were
  100. // printed to the screen so we don't put additional output
  101. // in something like an XML report. If we are printing to screen,
  102. // the report types would have already worked out who should
  103. // print the timer info.
  104. if ($this->config->interactive === false
  105. && ($toScreen === false
  106. || (($this->reporter->totalErrors + $this->reporter->totalWarnings) === 0 && $this->config->showProgress === true))
  107. ) {
  108. Util\Timing::printRunTime();
  109. }
  110. } catch (DeepExitException $e) {
  111. echo $e->getMessage();
  112. return $e->getCode();
  113. }//end try
  114. if ($numErrors === 0) {
  115. // No errors found.
  116. return 0;
  117. } else if ($this->reporter->totalFixable === 0) {
  118. // Errors found, but none of them can be fixed by PHPCBF.
  119. return 1;
  120. } else {
  121. // Errors found, and some can be fixed by PHPCBF.
  122. return 2;
  123. }
  124. }//end runPHPCS()
  125. /**
  126. * Run the PHPCBF script.
  127. *
  128. * @return array
  129. */
  130. public function runPHPCBF()
  131. {
  132. if (defined('PHP_CODESNIFFER_CBF') === false) {
  133. define('PHP_CODESNIFFER_CBF', true);
  134. }
  135. try {
  136. Util\Timing::startTiming();
  137. Runner::checkRequirements();
  138. // Creating the Config object populates it with all required settings
  139. // based on the CLI arguments provided to the script and any config
  140. // values the user has set.
  141. $this->config = new Config();
  142. // When processing STDIN, we can't output anything to the screen
  143. // or it will end up mixed in with the file output.
  144. if ($this->config->stdin === true) {
  145. $this->config->verbosity = 0;
  146. }
  147. // Init the run and load the rulesets to set additional config vars.
  148. $this->init();
  149. // Override some of the command line settings that might break the fixes.
  150. $this->config->generator = null;
  151. $this->config->explain = false;
  152. $this->config->interactive = false;
  153. $this->config->cache = false;
  154. $this->config->showSources = false;
  155. $this->config->recordErrors = false;
  156. $this->config->reportFile = null;
  157. $this->config->reports = ['cbf' => null];
  158. // If a standard tries to set command line arguments itself, some
  159. // may be blocked because PHPCBF is running, so stop the script
  160. // dying if any are found.
  161. $this->config->dieOnUnknownArg = false;
  162. $this->run();
  163. $this->reporter->printReports();
  164. echo PHP_EOL;
  165. Util\Timing::printRunTime();
  166. } catch (DeepExitException $e) {
  167. echo $e->getMessage();
  168. return $e->getCode();
  169. }//end try
  170. if ($this->reporter->totalFixed === 0) {
  171. // Nothing was fixed by PHPCBF.
  172. if ($this->reporter->totalFixable === 0) {
  173. // Nothing found that could be fixed.
  174. return 0;
  175. } else {
  176. // Something failed to fix.
  177. return 2;
  178. }
  179. }
  180. if ($this->reporter->totalFixable === 0) {
  181. // PHPCBF fixed all fixable errors.
  182. return 1;
  183. }
  184. // PHPCBF fixed some fixable errors, but others failed to fix.
  185. return 2;
  186. }//end runPHPCBF()
  187. /**
  188. * Exits if the minimum requirements of PHP_CodSniffer are not met.
  189. *
  190. * @return array
  191. */
  192. public function checkRequirements()
  193. {
  194. // Check the PHP version.
  195. if (PHP_VERSION_ID < 50400) {
  196. $error = 'ERROR: PHP_CodeSniffer requires PHP version 5.4.0 or greater.'.PHP_EOL;
  197. throw new DeepExitException($error, 3);
  198. }
  199. if (extension_loaded('tokenizer') === false) {
  200. $error = 'ERROR: PHP_CodeSniffer requires the tokenizer extension to be enabled.'.PHP_EOL;
  201. throw new DeepExitException($error, 3);
  202. }
  203. }//end checkRequirements()
  204. /**
  205. * Init the rulesets and other high-level settings.
  206. *
  207. * @return void
  208. */
  209. public function init()
  210. {
  211. if (defined('PHP_CODESNIFFER_CBF') === false) {
  212. define('PHP_CODESNIFFER_CBF', false);
  213. }
  214. // Ensure this option is enabled or else line endings will not always
  215. // be detected properly for files created on a Mac with the /r line ending.
  216. ini_set('auto_detect_line_endings', true);
  217. // Check that the standards are valid.
  218. foreach ($this->config->standards as $standard) {
  219. if (Util\Standards::isInstalledStandard($standard) === false) {
  220. // They didn't select a valid coding standard, so help them
  221. // out by letting them know which standards are installed.
  222. $error = 'ERROR: the "'.$standard.'" coding standard is not installed. ';
  223. ob_start();
  224. Util\Standards::printInstalledStandards();
  225. $error .= ob_get_contents();
  226. ob_end_clean();
  227. throw new DeepExitException($error, 3);
  228. }
  229. }
  230. // Saves passing the Config object into other objects that only need
  231. // the verbostity flag for deubg output.
  232. if (defined('PHP_CODESNIFFER_VERBOSITY') === false) {
  233. define('PHP_CODESNIFFER_VERBOSITY', $this->config->verbosity);
  234. }
  235. // Create this class so it is autoloaded and sets up a bunch
  236. // of PHP_CodeSniffer-specific token type constants.
  237. $tokens = new Util\Tokens();
  238. // Allow autoloading of custom files inside installed standards.
  239. $installedStandards = Standards::getInstalledStandardDetails();
  240. foreach ($installedStandards as $name => $details) {
  241. Autoload::addSearchPath($details['path'], $details['namespace']);
  242. }
  243. // The ruleset contains all the information about how the files
  244. // should be checked and/or fixed.
  245. try {
  246. $this->ruleset = new Ruleset($this->config);
  247. } catch (RuntimeException $e) {
  248. $error = 'ERROR: '.$e->getMessage().PHP_EOL.PHP_EOL;
  249. $error .= $this->config->printShortUsage(true);
  250. throw new DeepExitException($error, 3);
  251. }
  252. }//end init()
  253. /**
  254. * Performs the run.
  255. *
  256. * @return int The number of errors and warnings found.
  257. */
  258. private function run()
  259. {
  260. // The class that manages all reporters for the run.
  261. $this->reporter = new Reporter($this->config);
  262. // Include bootstrap files.
  263. foreach ($this->config->bootstrap as $bootstrap) {
  264. include $bootstrap;
  265. }
  266. if ($this->config->stdin === true) {
  267. $fileContents = $this->config->stdinContent;
  268. if ($fileContents === null) {
  269. $handle = fopen('php://stdin', 'r');
  270. stream_set_blocking($handle, true);
  271. $fileContents = stream_get_contents($handle);
  272. fclose($handle);
  273. }
  274. $todo = new FileList($this->config, $this->ruleset);
  275. $dummy = new DummyFile($fileContents, $this->ruleset, $this->config);
  276. $todo->addFile($dummy->path, $dummy);
  277. } else {
  278. if (empty($this->config->files) === true) {
  279. $error = 'ERROR: You must supply at least one file or directory to process.'.PHP_EOL.PHP_EOL;
  280. $error .= $this->config->printShortUsage(true);
  281. throw new DeepExitException($error, 3);
  282. }
  283. if (PHP_CODESNIFFER_VERBOSITY > 0) {
  284. echo 'Creating file list... ';
  285. }
  286. $todo = new FileList($this->config, $this->ruleset);
  287. if (PHP_CODESNIFFER_VERBOSITY > 0) {
  288. $numFiles = count($todo);
  289. echo "DONE ($numFiles files in queue)".PHP_EOL;
  290. }
  291. if ($this->config->cache === true) {
  292. if (PHP_CODESNIFFER_VERBOSITY > 0) {
  293. echo 'Loading cache... ';
  294. }
  295. Cache::load($this->ruleset, $this->config);
  296. if (PHP_CODESNIFFER_VERBOSITY > 0) {
  297. $size = Cache::getSize();
  298. echo "DONE ($size files in cache)".PHP_EOL;
  299. }
  300. }
  301. }//end if
  302. // Turn all sniff errors into exceptions.
  303. set_error_handler([$this, 'handleErrors']);
  304. // If verbosity is too high, turn off parallelism so the
  305. // debug output is clean.
  306. if (PHP_CODESNIFFER_VERBOSITY > 1) {
  307. $this->config->parallel = 1;
  308. }
  309. // If the PCNTL extension isn't installed, we can't fork.
  310. if (function_exists('pcntl_fork') === false) {
  311. $this->config->parallel = 1;
  312. }
  313. $lastDir = '';
  314. $numFiles = count($todo);
  315. if ($this->config->parallel === 1) {
  316. // Running normally.
  317. $numProcessed = 0;
  318. foreach ($todo as $path => $file) {
  319. if ($file->ignored === false) {
  320. $currDir = dirname($path);
  321. if ($lastDir !== $currDir) {
  322. if (PHP_CODESNIFFER_VERBOSITY > 0) {
  323. echo 'Changing into directory '.Common::stripBasepath($currDir, $this->config->basepath).PHP_EOL;
  324. }
  325. $lastDir = $currDir;
  326. }
  327. $this->processFile($file);
  328. } else if (PHP_CODESNIFFER_VERBOSITY > 0) {
  329. echo 'Skipping '.basename($file->path).PHP_EOL;
  330. }
  331. $numProcessed++;
  332. $this->printProgress($file, $numFiles, $numProcessed);
  333. }
  334. } else {
  335. // Batching and forking.
  336. $childProcs = [];
  337. $numPerBatch = ceil($numFiles / $this->config->parallel);
  338. for ($batch = 0; $batch < $this->config->parallel; $batch++) {
  339. $startAt = ($batch * $numPerBatch);
  340. if ($startAt >= $numFiles) {
  341. break;
  342. }
  343. $endAt = ($startAt + $numPerBatch);
  344. if ($endAt > $numFiles) {
  345. $endAt = $numFiles;
  346. }
  347. $childOutFilename = tempnam(sys_get_temp_dir(), 'phpcs-child');
  348. $pid = pcntl_fork();
  349. if ($pid === -1) {
  350. throw new RuntimeException('Failed to create child process');
  351. } else if ($pid !== 0) {
  352. $childProcs[] = [
  353. 'pid' => $pid,
  354. 'out' => $childOutFilename,
  355. ];
  356. } else {
  357. // Move forward to the start of the batch.
  358. $todo->rewind();
  359. for ($i = 0; $i < $startAt; $i++) {
  360. $todo->next();
  361. }
  362. // Reset the reporter to make sure only figures from this
  363. // file batch are recorded.
  364. $this->reporter->totalFiles = 0;
  365. $this->reporter->totalErrors = 0;
  366. $this->reporter->totalWarnings = 0;
  367. $this->reporter->totalFixable = 0;
  368. $this->reporter->totalFixed = 0;
  369. // Process the files.
  370. $pathsProcessed = [];
  371. ob_start();
  372. for ($i = $startAt; $i < $endAt; $i++) {
  373. $path = $todo->key();
  374. $file = $todo->current();
  375. if ($file->ignored === true) {
  376. continue;
  377. }
  378. $currDir = dirname($path);
  379. if ($lastDir !== $currDir) {
  380. if (PHP_CODESNIFFER_VERBOSITY > 0) {
  381. echo 'Changing into directory '.Common::stripBasepath($currDir, $this->config->basepath).PHP_EOL;
  382. }
  383. $lastDir = $currDir;
  384. }
  385. $this->processFile($file);
  386. $pathsProcessed[] = $path;
  387. $todo->next();
  388. }//end for
  389. $debugOutput = ob_get_contents();
  390. ob_end_clean();
  391. // Write information about the run to the filesystem
  392. // so it can be picked up by the main process.
  393. $childOutput = [
  394. 'totalFiles' => $this->reporter->totalFiles,
  395. 'totalErrors' => $this->reporter->totalErrors,
  396. 'totalWarnings' => $this->reporter->totalWarnings,
  397. 'totalFixable' => $this->reporter->totalFixable,
  398. 'totalFixed' => $this->reporter->totalFixed,
  399. ];
  400. $output = '<'.'?php'."\n".' $childOutput = ';
  401. $output .= var_export($childOutput, true);
  402. $output .= ";\n\$debugOutput = ";
  403. $output .= var_export($debugOutput, true);
  404. if ($this->config->cache === true) {
  405. $childCache = [];
  406. foreach ($pathsProcessed as $path) {
  407. $childCache[$path] = Cache::get($path);
  408. }
  409. $output .= ";\n\$childCache = ";
  410. $output .= var_export($childCache, true);
  411. }
  412. $output .= ";\n?".'>';
  413. file_put_contents($childOutFilename, $output);
  414. exit($pid);
  415. }//end if
  416. }//end for
  417. $this->processChildProcs($childProcs);
  418. }//end if
  419. restore_error_handler();
  420. if (PHP_CODESNIFFER_VERBOSITY === 0
  421. && $this->config->interactive === false
  422. && $this->config->showProgress === true
  423. ) {
  424. echo PHP_EOL.PHP_EOL;
  425. }
  426. if ($this->config->cache === true) {
  427. Cache::save();
  428. }
  429. $ignoreWarnings = Config::getConfigData('ignore_warnings_on_exit');
  430. $ignoreErrors = Config::getConfigData('ignore_errors_on_exit');
  431. $return = ($this->reporter->totalErrors + $this->reporter->totalWarnings);
  432. if ($ignoreErrors !== null) {
  433. $ignoreErrors = (bool) $ignoreErrors;
  434. if ($ignoreErrors === true) {
  435. $return -= $this->reporter->totalErrors;
  436. }
  437. }
  438. if ($ignoreWarnings !== null) {
  439. $ignoreWarnings = (bool) $ignoreWarnings;
  440. if ($ignoreWarnings === true) {
  441. $return -= $this->reporter->totalWarnings;
  442. }
  443. }
  444. return $return;
  445. }//end run()
  446. /**
  447. * Converts all PHP errors into exceptions.
  448. *
  449. * This method forces a sniff to stop processing if it is not
  450. * able to handle a specific piece of code, instead of continuing
  451. * and potentially getting into a loop.
  452. *
  453. * @param int $code The level of error raised.
  454. * @param string $message The error message.
  455. * @param string $file The path of the file that raised the error.
  456. * @param int $line The line number the error was raised at.
  457. *
  458. * @return void
  459. */
  460. public function handleErrors($code, $message, $file, $line)
  461. {
  462. if ((error_reporting() & $code) === 0) {
  463. // This type of error is being muted.
  464. return true;
  465. }
  466. throw new RuntimeException("$message in $file on line $line");
  467. }//end handleErrors()
  468. /**
  469. * Processes a single file, including checking and fixing.
  470. *
  471. * @param \PHP_CodeSniffer\Files\File $file The file to be processed.
  472. *
  473. * @return void
  474. */
  475. public function processFile($file)
  476. {
  477. if (PHP_CODESNIFFER_VERBOSITY > 0) {
  478. $startTime = microtime(true);
  479. echo 'Processing '.basename($file->path).' ';
  480. if (PHP_CODESNIFFER_VERBOSITY > 1) {
  481. echo PHP_EOL;
  482. }
  483. }
  484. try {
  485. $file->process();
  486. if (PHP_CODESNIFFER_VERBOSITY > 0) {
  487. $timeTaken = ((microtime(true) - $startTime) * 1000);
  488. if ($timeTaken < 1000) {
  489. $timeTaken = round($timeTaken);
  490. echo "DONE in {$timeTaken}ms";
  491. } else {
  492. $timeTaken = round(($timeTaken / 1000), 2);
  493. echo "DONE in $timeTaken secs";
  494. }
  495. if (PHP_CODESNIFFER_CBF === true) {
  496. $errors = $file->getFixableCount();
  497. echo " ($errors fixable violations)".PHP_EOL;
  498. } else {
  499. $errors = $file->getErrorCount();
  500. $warnings = $file->getWarningCount();
  501. echo " ($errors errors, $warnings warnings)".PHP_EOL;
  502. }
  503. }
  504. } catch (\Exception $e) {
  505. $error = 'An error occurred during processing; checking has been aborted. The error message was: '.$e->getMessage();
  506. $file->addErrorOnLine($error, 1, 'Internal.Exception');
  507. }//end try
  508. $this->reporter->cacheFileReport($file, $this->config);
  509. if ($this->config->interactive === true) {
  510. /*
  511. Running interactively.
  512. Print the error report for the current file and then wait for user input.
  513. */
  514. // Get current violations and then clear the list to make sure
  515. // we only print violations for a single file each time.
  516. $numErrors = null;
  517. while ($numErrors !== 0) {
  518. $numErrors = ($file->getErrorCount() + $file->getWarningCount());
  519. if ($numErrors === 0) {
  520. continue;
  521. }
  522. $this->reporter->printReport('full');
  523. echo '<ENTER> to recheck, [s] to skip or [q] to quit : ';
  524. $input = fgets(STDIN);
  525. $input = trim($input);
  526. switch ($input) {
  527. case 's':
  528. break(2);
  529. case 'q':
  530. throw new DeepExitException('', 0);
  531. default:
  532. // Repopulate the sniffs because some of them save their state
  533. // and only clear it when the file changes, but we are rechecking
  534. // the same file.
  535. $file->ruleset->populateTokenListeners();
  536. $file->reloadContent();
  537. $file->process();
  538. $this->reporter->cacheFileReport($file, $this->config);
  539. break;
  540. }
  541. }//end while
  542. }//end if
  543. // Clean up the file to save (a lot of) memory.
  544. $file->cleanUp();
  545. }//end processFile()
  546. /**
  547. * Waits for child processes to complete and cleans up after them.
  548. *
  549. * The reporting information returned by each child process is merged
  550. * into the main reporter class.
  551. *
  552. * @param array $childProcs An array of child processes to wait for.
  553. *
  554. * @return void
  555. */
  556. private function processChildProcs($childProcs)
  557. {
  558. $numProcessed = 0;
  559. $totalBatches = count($childProcs);
  560. while (count($childProcs) > 0) {
  561. foreach ($childProcs as $key => $procData) {
  562. $res = pcntl_waitpid($procData['pid'], $status, WNOHANG);
  563. if ($res === $procData['pid']) {
  564. if (file_exists($procData['out']) === true) {
  565. include $procData['out'];
  566. if (isset($childOutput) === true) {
  567. $this->reporter->totalFiles += $childOutput['totalFiles'];
  568. $this->reporter->totalErrors += $childOutput['totalErrors'];
  569. $this->reporter->totalWarnings += $childOutput['totalWarnings'];
  570. $this->reporter->totalFixable += $childOutput['totalFixable'];
  571. $this->reporter->totalFixed += $childOutput['totalFixed'];
  572. }
  573. if (isset($debugOutput) === true) {
  574. echo $debugOutput;
  575. }
  576. if (isset($childCache) === true) {
  577. foreach ($childCache as $path => $cache) {
  578. Cache::set($path, $cache);
  579. }
  580. }
  581. unlink($procData['out']);
  582. unset($childProcs[$key]);
  583. $numProcessed++;
  584. // Fake a processed file so we can print progress output for the batch.
  585. $file = new DummyFile(null, $this->ruleset, $this->config);
  586. $file->setErrorCounts(
  587. $childOutput['totalErrors'],
  588. $childOutput['totalWarnings'],
  589. $childOutput['totalFixable'],
  590. $childOutput['totalFixed']
  591. );
  592. $this->printProgress($file, $totalBatches, $numProcessed);
  593. }//end if
  594. }//end if
  595. }//end foreach
  596. }//end while
  597. }//end processChildProcs()
  598. /**
  599. * Print progress information for a single processed file.
  600. *
  601. * @param File $file The file that was processed.
  602. * @param int $numFiles The total number of files to process.
  603. * @param int $numProcessed The number of files that have been processed,
  604. * including this one.
  605. *
  606. * @return void
  607. */
  608. public function printProgress($file, $numFiles, $numProcessed)
  609. {
  610. if (PHP_CODESNIFFER_VERBOSITY > 0
  611. || $this->config->showProgress === false
  612. ) {
  613. return;
  614. }
  615. // Show progress information.
  616. if ($file->ignored === true) {
  617. echo 'S';
  618. } else {
  619. $errors = $file->getErrorCount();
  620. $warnings = $file->getWarningCount();
  621. $fixable = $file->getFixableCount();
  622. $fixed = $file->getFixedCount();
  623. if (PHP_CODESNIFFER_CBF === true) {
  624. // Files with fixed errors or warnings are F (green).
  625. // Files with unfixable errors or warnings are E (red).
  626. // Files with no errors or warnings are . (black).
  627. if ($fixable > 0) {
  628. if ($this->config->colors === true) {
  629. echo "\033[31m";
  630. }
  631. echo 'E';
  632. if ($this->config->colors === true) {
  633. echo "\033[0m";
  634. }
  635. } else if ($fixed > 0) {
  636. if ($this->config->colors === true) {
  637. echo "\033[32m";
  638. }
  639. echo 'F';
  640. if ($this->config->colors === true) {
  641. echo "\033[0m";
  642. }
  643. } else {
  644. echo '.';
  645. }//end if
  646. } else {
  647. // Files with errors are E (red).
  648. // Files with fixable errors are E (green).
  649. // Files with warnings are W (yellow).
  650. // Files with fixable warnings are W (green).
  651. // Files with no errors or warnings are . (black).
  652. if ($errors > 0) {
  653. if ($this->config->colors === true) {
  654. if ($fixable > 0) {
  655. echo "\033[32m";
  656. } else {
  657. echo "\033[31m";
  658. }
  659. }
  660. echo 'E';
  661. if ($this->config->colors === true) {
  662. echo "\033[0m";
  663. }
  664. } else if ($warnings > 0) {
  665. if ($this->config->colors === true) {
  666. if ($fixable > 0) {
  667. echo "\033[32m";
  668. } else {
  669. echo "\033[33m";
  670. }
  671. }
  672. echo 'W';
  673. if ($this->config->colors === true) {
  674. echo "\033[0m";
  675. }
  676. } else {
  677. echo '.';
  678. }//end if
  679. }//end if
  680. }//end if
  681. $numPerLine = 60;
  682. if ($numProcessed !== $numFiles && ($numProcessed % $numPerLine) !== 0) {
  683. return;
  684. }
  685. $percent = round(($numProcessed / $numFiles) * 100);
  686. $padding = (strlen($numFiles) - strlen($numProcessed));
  687. if ($numProcessed === $numFiles && $numFiles > $numPerLine) {
  688. $padding += ($numPerLine - ($numFiles - (floor($numFiles / $numPerLine) * $numPerLine)));
  689. }
  690. echo str_repeat(' ', $padding)." $numProcessed / $numFiles ($percent%)".PHP_EOL;
  691. }//end printProgress()
  692. }//end class