Browse Source

Add configuration exception classes and exit codes

Possum 9 years ago
parent
commit
87242c89f4

+ 10 - 1
README.rst

@@ -124,7 +124,9 @@ problems as possible on a given file or directory:
     php php-cs-fixer.phar fix /path/to/dir
     php php-cs-fixer.phar fix /path/to/file
 
-The ``--verbose`` option show applied fixers. When using ``txt`` format (default one) it will also displays progress notification.
+The ``--verbose`` option show applied fixers. When using ``txt`` format it will also displays progress notification.
+
+The ``--format`` option for the output format. Support are ``txt`` (default one), ``json`` and ``xml``.
 
 The ``--level`` option limits the fixers to apply on the
 project:
@@ -778,6 +780,13 @@ speed up further runs.
         ->setUsingCache(true)
     ;
 
+Exit codes
+----------
+*  0 OK
+*  1 No changes made
+* 16 Configuration error of the application
+* 32 Configuration error of a Fixer
+
 Helpers
 -------
 

+ 32 - 0
Symfony/CS/ConfigurationException/InvalidConfigurationException.php

@@ -0,0 +1,32 @@
+<?php
+
+/*
+ * This file is part of the PHP CS utility.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace Symfony\CS\ConfigurationException;
+
+use Symfony\CS\Console\Command\FixCommand;
+
+/**
+ * Exceptions of this type are thrown on misconfiguration of the Fixer.
+ *
+ * @author SpacePossum
+ *
+ * @internal
+ */
+class InvalidConfigurationException extends \InvalidArgumentException
+{
+    /**
+     * @param string $message
+     */
+    public function __construct($message)
+    {
+        parent::__construct($message, FixCommand::EXIT_STATUS_FLAG_HAS_INVALID_CONFIG);
+    }
+}

+ 33 - 0
Symfony/CS/ConfigurationException/InvalidFixerConfigurationException.php

@@ -0,0 +1,33 @@
+<?php
+
+/*
+ * This file is part of the PHP CS utility.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace Symfony\CS\ConfigurationException;
+
+use Symfony\CS\Console\Command\FixCommand;
+
+/**
+ * Exception thrown by Fixers on misconfiguration.
+ *
+ * @author SpacePossum
+ *
+ * @internal
+ */
+class InvalidFixerConfigurationException extends InvalidConfigurationException
+{
+    /**
+     * @param string $fixerName
+     * @param string $message
+     */
+    public function __construct($fixerName, $message)
+    {
+        parent::__construct(sprintf('[%s] %s', $fixerName, $message), FixCommand::EXIT_STATUS_FLAG_HAS_INVALID_FIXER_CONFIG);
+    }
+}

+ 37 - 5
Symfony/CS/ConfigurationResolver.php

@@ -11,6 +11,8 @@
 
 namespace Symfony\CS;
 
+use Symfony\CS\ConfigurationException\InvalidConfigurationException;
+
 /**
  * The resolver that resolves configuration to use by command line options and config.
  *
@@ -44,6 +46,11 @@ class ConfigurationResolver
         'progress' => null,
     );
 
+    /**
+     * @var string
+     */
+    private $format;
+
     public function setAllFixers(array $allFixers)
     {
         $this->allFixers = $allFixers;
@@ -83,6 +90,7 @@ class ConfigurationResolver
     {
         $this->resolveByLevel();
         $this->resolveByNames();
+        $this->resolveFormat();
 
         return $this;
     }
@@ -97,6 +105,16 @@ class ConfigurationResolver
         return $this->fixers;
     }
 
+    /**
+     * Returns output format.
+     *
+     * @return string
+     */
+    public function getFormat()
+    {
+        return $this->format;
+    }
+
     public function getProgress()
     {
         // TODO: following condition should be removed on 2.0 line
@@ -158,6 +176,24 @@ class ConfigurationResolver
         }
     }
 
+    protected function resolveFormat()
+    {
+        if (array_key_exists('format', $this->options)) {
+            $format = $this->options['format'];
+        } elseif (method_exists($this->config, 'getFormat')) {
+            $format = $this->config->getFormat();
+        } else {
+            $format = 'txt'; // default
+        }
+
+        static $formats = array('txt', 'xml', 'json');
+        if (!in_array($format, $formats, true)) {
+            throw new InvalidConfigurationException(sprintf('The format "%s" is not defined, supported are %s.', $format, implode(', ', $formats)));
+        }
+
+        $this->format = $format;
+    }
+
     protected function parseLevel()
     {
         static $levelMap = array(
@@ -172,7 +208,7 @@ class ConfigurationResolver
 
         if (null !== $levelOption) {
             if (!isset($levelMap[$levelOption])) {
-                throw new \InvalidArgumentException(sprintf('The level "%s" is not defined.', $levelOption));
+                throw new InvalidConfigurationException(sprintf('The level "%s" is not defined.', $levelOption));
             }
 
             return $levelMap[$levelOption];
@@ -187,8 +223,6 @@ class ConfigurationResolver
                 return $this->config->getLevel();
             }
         }
-
-        return;
     }
 
     protected function parseFixers()
@@ -200,7 +234,5 @@ class ConfigurationResolver
         if (null === $this->options['level']) {
             return $this->config->getFixers();
         }
-
-        return;
     }
 }

+ 41 - 19
Symfony/CS/Console/Command/FixCommand.php

@@ -22,6 +22,7 @@ use Symfony\Component\Filesystem\Filesystem;
 use Symfony\Component\Stopwatch\Stopwatch;
 use Symfony\CS\Config\Config;
 use Symfony\CS\ConfigInterface;
+use Symfony\CS\ConfigurationException\InvalidConfigurationException;
 use Symfony\CS\ConfigurationResolver;
 use Symfony\CS\ErrorsManager;
 use Symfony\CS\Fixer;
@@ -36,6 +37,9 @@ use Symfony\CS\Utils;
  */
 class FixCommand extends Command
 {
+    const EXIT_STATUS_FLAG_HAS_INVALID_CONFIG = 16;
+    const EXIT_STATUS_FLAG_HAS_INVALID_FIXER_CONFIG = 32;
+
     /**
      * EventDispatcher instance.
      *
@@ -118,7 +122,9 @@ problems as possible on a given file or directory:
     <info>php %command.full_name% /path/to/dir</info>
     <info>php %command.full_name% /path/to/file</info>
 
-The <comment>--verbose</comment> option show applied fixers. When using ``txt`` format (default one) it will also displays progress notification.
+The <comment>--verbose</comment> option show applied fixers. When using ``txt`` format it will also displays progress notification.
+
+The <comment>--format</comment> option for the output format. Support are ``txt`` (default one), ``json`` and ``xml``.
 
 The <comment>--level</comment> option limits the fixers to apply on the
 project:
@@ -262,6 +268,13 @@ speed up further runs.
     ;
 
     ?>
+
+Exit codes
+----------
+*  0 OK
+*  1 No changes made
+* 16 Configuration error of the application
+* 32 Configuration error of a Fixer
 EOF
             );
     }
@@ -271,6 +284,15 @@ EOF
      */
     protected function execute(InputInterface $input, OutputInterface $output)
     {
+        // setup output
+        $stdErr = ($output instanceof ConsoleOutputInterface) ? $output->getErrorOutput() : null;
+        if ($stdErr && extension_loaded('xdebug')) {
+            $stdErr->writeln(sprintf($stdErr->isDecorated() ? '<bg=yellow;fg=black;>%s</>' : '%s', 'You are running php-cs-fixer with xdebug enabled. This has a major impact on runtime performance.'));
+        }
+
+        $verbosity = $output->getVerbosity();
+
+        // setup input
         $path = $input->getArgument('path');
 
         $stdin = false;
@@ -289,6 +311,7 @@ EOF
             }
         }
 
+        // setup configuration location
         $configFile = $input->getOption('config-file');
         if (null === $configFile) {
             $configDir = $path;
@@ -312,13 +335,13 @@ EOF
             }
 
             if (null === $config) {
-                throw new \InvalidArgumentException(sprintf('The configuration "%s" is not defined.', $input->getOption('config')));
+                throw new InvalidConfigurationException(sprintf('The configuration "%s" is not defined.', $input->getOption('config')));
             }
         } elseif (file_exists($configFile)) {
             $config = include $configFile;
             // verify that the config has an instance of Config
             if (!$config instanceof Config) {
-                throw new \UnexpectedValueException(sprintf('The config file "%s" does not return a "Symfony\CS\Config\Config" instance. Got: "%s".', $configFile, is_object($config) ? get_class($config) : gettype($config)));
+                throw new InvalidConfigurationException(sprintf('The config file "%s" does not return a "Symfony\CS\Config\Config" instance. Got: "%s".', $configFile, is_object($config) ? get_class($config) : gettype($config)));
             }
 
             if ('txt' === $input->getOption('format')) {
@@ -328,10 +351,7 @@ EOF
             $config = $this->defaultConfig;
         }
 
-        if ($config->usingLinter()) {
-            $this->fixer->setLintManager(new LintManager());
-        }
-
+        // setup location of source(s) to fix
         if (is_file($path)) {
             $config->finder(new \ArrayIterator(array(new \SplFileInfo($path))));
         } elseif ($stdin) {
@@ -340,13 +360,11 @@ EOF
             $config->setDir($path);
         }
 
-        if ($output instanceof ConsoleOutputInterface && extension_loaded('xdebug')) {
-            $stdErr = $output->getErrorOutput();
-            $stdErr->writeln(sprintf($stdErr->isDecorated() ? '<bg=yellow;fg=black;>%s</>' : '%s', 'You are running php-cs-fixer with xdebug enabled. This has a major impact on runtime performance.'));
+        // setup Linter
+        if ($config->usingLinter()) {
+            $this->fixer->setLintManager(new LintManager());
         }
 
-        $verbosity = $output->getVerbosity();
-
         // register custom fixers from config
         $this->fixer->registerCustomFixers($config->getCustomFixers());
 
@@ -358,6 +376,7 @@ EOF
                 'level' => $input->getOption('level'),
                 'fixers' => $input->getOption('fixers'),
                 'progress' => (OutputInterface::VERBOSITY_VERBOSE <= $verbosity) && 'txt' === $input->getOption('format'),
+                'format' => $input->getOption('format'),
             ))
             ->resolve();
 
@@ -394,13 +413,18 @@ EOF
 
         $i = 1;
 
-        switch ($input->getOption('format')) {
+        switch ($resolver->getFormat()) {
             case 'txt':
-                foreach ($changed as $file => $fixResult) {
-                    $output->write(sprintf('%4d) %s', $i++, $file));
 
-                    if (OutputInterface::VERBOSITY_VERBOSE <= $verbosity) {
-                        $output->write(sprintf(' (<comment>%s</comment>)', implode(', ', $fixResult['appliedFixers'])));
+                $fixerDetailLine = false;
+                if (OutputInterface::VERBOSITY_VERBOSE <= $verbosity) {
+                    $fixerDetailLine = $output->isDecorated() ? ' (<comment>%s</comment>)' : ' %s';
+                }
+
+                foreach ($changed as $file => $fixResult) {
+                    if ($fixerDetailLine) {
+                        $output->write(sprintf('%4d) %s', $i++, $file));
+                        $output->write(sprintf($fixerDetailLine, implode(', ', $fixResult['appliedFixers'])));
                     }
 
                     if ($input->getOption('diff')) {
@@ -505,8 +529,6 @@ EOF
 
                 $output->write(json_encode($json));
                 break;
-            default:
-                throw new \InvalidArgumentException(sprintf('The format "%s" is not defined.', $input->getOption('format')));
         }
 
         if (!$this->errorsManager->isEmpty()) {

+ 5 - 0
Symfony/CS/Fixer/Contrib/HeaderCommentFixer.php

@@ -12,6 +12,7 @@
 namespace Symfony\CS\Fixer\Contrib;
 
 use Symfony\CS\AbstractFixer;
+use Symfony\CS\ConfigurationException\InvalidFixerConfigurationException;
 use Symfony\CS\Tokenizer\Token;
 use Symfony\CS\Tokenizer\Tokens;
 
@@ -34,6 +35,10 @@ class HeaderCommentFixer extends AbstractFixer
      */
     public static function setHeader($header)
     {
+        if (!is_string($header)) {
+            throw new InvalidFixerConfigurationException('header_comment', sprintf('Header configuration is invalid. Expected "string", got "%s".', is_object($header) ? get_class($header) : gettype($header)));
+        }
+
         self::$header = trim((string) $header);
         self::$headerComment = '';
 

+ 3 - 2
Symfony/CS/Fixer/Contrib/PhpUnitConstructFixer.php

@@ -12,6 +12,7 @@
 namespace Symfony\CS\Fixer\Contrib;
 
 use Symfony\CS\AbstractFixer;
+use Symfony\CS\ConfigurationException\InvalidFixerConfigurationException;
 use Symfony\CS\Tokenizer\Tokens;
 
 /**
@@ -36,8 +37,8 @@ final class PhpUnitConstructFixer extends AbstractFixer
     public function configure(array $usingMethods)
     {
         foreach ($usingMethods as $method => $fix) {
-            if (!isset($this->configuration[$method])) {
-                throw new \InvalidArgumentException(sprintf('Configured method "%s" cannot be fixed by this fixer.', $method));
+            if (!array_key_exists($method, $this->configuration)) {
+                throw new InvalidFixerConfigurationException($this->getName(), sprintf('Configured method "%s" cannot be fixed by this fixer.', $method));
             }
 
             $this->configuration[$method] = $fix;

+ 9 - 0
Symfony/CS/Tests/Fixer/Contrib/HeaderCommentFixerTest.php

@@ -197,4 +197,13 @@ EOH;
         $input = "<?php\n";
         $this->makeTest($expected, $input);
     }
+
+    /**
+     * @expectedException \Symfony\CS\ConfigurationException\InvalidFixerConfigurationException
+     * @expectedExceptionMessage [header_comment] Header configuration is invalid. Expected "string", got "stdClass".
+     */
+    public function testInvalidConfig()
+    {
+        HeaderCommentFixer::setHeader(new \stdClass());
+    }
 }

+ 11 - 0
Symfony/CS/Tests/Fixer/Contrib/PhpUnitConstructFixerTest.php

@@ -109,6 +109,17 @@ class PhpUnitConstructFixerTest extends AbstractFixerTestBase
         );
     }
 
+    /**
+     * @expectedException \Symfony\CS\ConfigurationException\InvalidFixerConfigurationException
+     * @expectedExceptionMessage Configured method "__TEST__" cannot be fixed by this fixer.
+     */
+    public function testInvalidConfig()
+    {
+        /** @var PhpUnitConstructFixer $fixer */
+        $fixer = $this->getFixer();
+        $fixer->configure(array('__TEST__' => 'abc'));
+    }
+
     private function generateCases($expectedTemplate, $inputTemplate)
     {
         $cases = array();