Browse Source

feature #1913 Introduce path-mode CLI option (keradus)

This PR was merged into the 2.0-dev branch.

Discussion
----------

Introduce path-mode CLI option

Closes #1900

Commits
-------

56892f3 Introduce path-mode CLI option
Dariusz Ruminski 8 years ago
parent
commit
fc572fdfa5

+ 2 - 2
README.rst

@@ -896,9 +896,9 @@ Then, add the following command to your CI:
 
 .. code-block:: bash
 
-    $ vendor/bin/php-cs-fixer fix --config=.php_cs.dist `git diff --name-only $COMMIT_RANGE`
+    $ vendor/bin/php-cs-fixer fix --config=.php_cs.dist --path-mode=intersection `git diff --name-only $COMMIT_RANGE`
 
-Where ``$COMMIT_RANGE`` is your range of commits, eg ``$TRAVIS_COMMIT_RANGE`` or ``HEAD^^..HEAD``.
+Where ``$COMMIT_RANGE`` is your range of commits, eg ``$TRAVIS_COMMIT_RANGE`` or ``HEAD~..HEAD``.
 
 Exit codes
 ----------

+ 19 - 15
UPGRADE.md

@@ -20,25 +20,29 @@ Finally, the caching mechanism is enabled by default.
 CLI options
 -----------
 
-1.x           | 2.0           | Description                 | Note
-------------- | ------------- | --------------------------- | ----
-              | --allow-risky | Are risky fixers allowed    |
-              | --cache-file  | The path to the cache file  | option was added
---config      |               | Config class codename       | option was removed
---config-file | --config      | The path to a .php_cs file  | option was renamed
---diff        | --diff        | Show diff                   |
---dry-run     | --dry-run     | Run in dry-run mode         |
---fixers      |               | Coding standard fixers      | options was removed, see --rules
---format      | --format      | Choose format               |
---level       |               | Coding standard level       | options was removed, see --rules
-              | --rules       | Rules to be used            | option was added
-              | --using-cache | Does cache should be used   | option was added
+1.x           | 2.0           | Description                                     | Note
+------------- | ------------- | ----------------------------------------------- | ----
+              | --allow-risky | Are risky fixers allowed                        |
+              | --cache-file  | The path to the cache file                      | option was added
+--config      |               | Config class codename                           | option was removed
+--config-file | --config      | The path to a .php_cs file                      | option was renamed
+--diff        | --diff        | Show diff                                       |
+--dry-run     | --dry-run     | Run in dry-run mode                             |
+--fixers      |               | Coding standard fixers                          | options was removed, see --rules
+--format      | --format      | Choose format                                   |
+--level       |               | Coding standard level                           | options was removed, see --rules
+              | --rules       | Rules to be used                                | option was added
+              | --using-cache | Does cache should be used                       | option was added
+              | --path-mode   | Should the finder from configuration be         | option was added
+              |               | overriden or intersected with `path` argument   |
 
 CLI argument
 ------------
 
-On 1.x line `path` argument allows you to specify the dir/file that will be fixed.
-On 2.x line `path` argument is a bit different. It is a mask for finder you are using (defined in your configuration file or default one). Only files pointed by both finder and CLI `path` argument will be fixed. Also, you may now pass multiple paths.
+On 2.x line `path` argument is an array, so you may pass multiple paths.
+
+Intersection path mode makes the `path` argument a mask for finder you have defined in your configuration file.
+Only files pointed by both finder and CLI `path` argument will be fixed.
 
 Exit codes
 ----------

+ 4 - 2
src/Console/Command/FixCommand.php

@@ -101,6 +101,7 @@ final class FixCommand extends Command
             ->setDefinition(
                 array(
                     new InputArgument('path', InputArgument::IS_ARRAY, 'The path', null),
+                    new InputOption('path-mode', '', InputOption::VALUE_REQUIRED, 'Specify path mode (can be override or intersection)', 'override'),
                     new InputOption('allow-risky', '', InputOption::VALUE_REQUIRED, 'Are risky fixers allowed (can be yes or no)', null),
                     new InputOption('config', '', InputOption::VALUE_REQUIRED, 'The path to a .php_cs file ', null),
                     new InputOption('dry-run', '', InputOption::VALUE_NONE, 'Only shows which files would have been modified'),
@@ -261,9 +262,9 @@ Require ``fabpot/php-cs-fixer`` as a `dev`` dependency:
 
 Then, add the following command to your CI:
 
-    $ vendor/bin/php-cs-fixer fix --config=.php_cs.dist `git diff --name-only \$COMMIT_RANGE`
+    $ vendor/bin/php-cs-fixer fix --config=.php_cs.dist --path-mode=intersection `git diff --name-only \$COMMIT_RANGE`
 
-Where ``\$COMMIT_RANGE`` is your range of commits, eg ``\$TRAVIS_COMMIT_RANGE`` or ``HEAD^^..HEAD``.
+Where ``\$COMMIT_RANGE`` is your range of commits, eg ``\$TRAVIS_COMMIT_RANGE`` or ``HEAD~..HEAD``.
 
 Exit codes
 ----------
@@ -296,6 +297,7 @@ EOF
                 'dry-run' => $input->getOption('dry-run'),
                 'rules' => $input->getOption('rules'),
                 'path' => $input->getArgument('path'),
+                'path-mode' => $input->getOption('path-mode'),
                 'progress' => (OutputInterface::VERBOSITY_VERBOSE <= $verbosity) && 'txt' === $input->getOption('format'),
                 'using-cache' => $input->getOption('using-cache'),
                 'cache-file' => $input->getOption('cache-file'),

+ 57 - 27
src/Console/ConfigurationResolver.php

@@ -33,6 +33,9 @@ use Symfony\Component\Finder\Finder as SymfonyFinder;
  */
 final class ConfigurationResolver
 {
+    const PATH_MODE_OVERRIDE = 'override';
+    const PATH_MODE_INTERSECTION = 'intersection';
+
     /**
      * @var bool
      */
@@ -92,6 +95,7 @@ final class ConfigurationResolver
         'dry-run' => null,
         'format' => 'txt',
         'path' => array(),
+        'path-mode' => self::PATH_MODE_OVERRIDE,
         'progress' => null,
         'using-cache' => null,
         'cache-file' => null,
@@ -206,6 +210,7 @@ final class ConfigurationResolver
      */
     public function resolve()
     {
+        $this->resolvePathMode();
         $this->resolvePath();
         $this->resolveIsStdIn();
         $this->resolveIsDryRun();
@@ -403,9 +408,7 @@ final class ConfigurationResolver
             return;
         }
 
-        if (empty($this->path)) {
-            return;
-        }
+        $isIntersectionPathMode = self::PATH_MODE_INTERSECTION === $this->options['path-mode'];
 
         $paths = array_filter(array_map(
             function ($path) {
@@ -415,7 +418,9 @@ final class ConfigurationResolver
         ));
 
         if (empty($paths)) {
-            $this->config->finder(new \ArrayIterator(array()));
+            if ($isIntersectionPathMode) {
+                $this->config->finder(new \ArrayIterator(array()));
+            }
 
             return;
         }
@@ -435,37 +440,45 @@ final class ConfigurationResolver
             }
         }
 
-        // - when we already have a valid finder - create intersection iterator of current finder and provided path
-        // - when we don't have valid finder - prepare new iterator
-        $iterator = null;
         $currentFinder = $this->config->getFinder();
+        $nestedFinder = null;
+        $iterator = null;
 
-        $callback = function (\SplFileInfo $current) use ($pathsByType) {
-            $currentRealPath = $current->getRealPath();
+        try {
+            $nestedFinder = $currentFinder instanceof \IteratorAggregate ? $currentFinder->getIterator() : $currentFinder;
+        } catch (\Exception $e) {
+        }
 
-            if (in_array($currentRealPath, $pathsByType['file'], true)) {
-                return true;
+        if ($isIntersectionPathMode) {
+            if (null === $nestedFinder) {
+                throw new InvalidConfigurationException(
+                    'Cannot create intersection with not-fully defined Finder in configuration file.'
+                );
             }
 
-            foreach ($pathsByType['dir'] as $path) {
-                if (0 === strpos($currentRealPath, $path)) {
-                    return true;
-                }
-            }
+            $iterator = new \CallbackFilterIterator(
+                $nestedFinder,
+                function (\SplFileInfo $current) use ($pathsByType) {
+                    $currentRealPath = $current->getRealPath();
 
-            return false;
-        };
+                    if (in_array($currentRealPath, $pathsByType['file'], true)) {
+                        return true;
+                    }
 
-        try {
-            $iterator = new \CallbackFilterIterator(
-                $currentFinder instanceof \IteratorAggregate ? $currentFinder->getIterator() : $currentFinder,
-                $callback
+                    foreach ($pathsByType['dir'] as $path) {
+                        if (0 === strpos($currentRealPath, $path)) {
+                            return true;
+                        }
+                    }
+
+                    return false;
+                }
             );
-        } catch (\LogicException $e) {
-            $iterator = $currentFinder instanceof SymfonyFinder
-                ? $currentFinder
-                : Finder::create();
-            $iterator->in($pathsByType['dir'])->append($pathsByType['file']);
+        } elseif ($currentFinder instanceof SymfonyFinder && null === $nestedFinder) {
+            // finder from configuration Symfony finder and it is not fully defined, we may fulfill it
+            $iterator = $currentFinder->in($pathsByType['dir'])->append($pathsByType['file']);
+        } else {
+            $iterator = Finder::create()->in($pathsByType['dir'])->append($pathsByType['file']);
         }
 
         $this->config->finder($iterator);
@@ -541,6 +554,23 @@ final class ConfigurationResolver
         $this->isStdIn = 1 === count($this->options['path']) && '-' === $this->options['path'][0];
     }
 
+    private function resolvePathMode()
+    {
+        $modes = array(self::PATH_MODE_OVERRIDE, self::PATH_MODE_INTERSECTION);
+
+        if (!in_array(
+            $this->options['path-mode'],
+            $modes,
+            true
+        )) {
+            throw new InvalidConfigurationException(sprintf(
+                'The path-mode "%s" is not defined, supported are %s.',
+                $this->options['path-mode'],
+                implode(', ', $modes)
+            ));
+        }
+    }
+
     /**
      * Resolve path based on path option.
      */

+ 95 - 32
tests/Console/ConfigurationResolverTest.php

@@ -277,7 +277,7 @@ final class ConfigurationResolverTest extends \PHPUnit_Framework_TestCase
         $this->assertSame(array(__DIR__), $this->resolver->getPath());
     }
 
-    public function testResolvePathWithFileThatIsExcludedDirectly()
+    public function testResolvePathWithFileThatIsExcludedDirectlyOverridePathMode()
     {
         $this->config->getFinder()
             ->in(__DIR__)
@@ -287,10 +287,38 @@ final class ConfigurationResolverTest extends \PHPUnit_Framework_TestCase
             ->setOption('path', array(__FILE__))
             ->resolve();
 
+        $this->assertCount(1, $this->resolver->getConfig()->getFinder());
+    }
+
+    public function testResolvePathWithFileThatIsExcludedDirectlyIntersectionPathMode()
+    {
+        $this->config->getFinder()
+            ->in(__DIR__)
+            ->notPath(basename(__FILE__));
+
+        $this->resolver
+            ->setOption('path', array(__FILE__))
+            ->setOption('path-mode', 'intersection')
+            ->resolve();
+
         $this->assertCount(0, $this->resolver->getConfig()->getFinder());
     }
 
-    public function testResolvePathWithFileThatIsExcludedByDir()
+    public function testResolvePathWithFileThatIsExcludedByDirOverridePathMode()
+    {
+        $dir = dirname(__DIR__);
+        $this->config->getFinder()
+            ->in($dir)
+            ->exclude(basename(__DIR__));
+
+        $this->resolver
+            ->setOption('path', array(__FILE__))
+            ->resolve();
+
+        $this->assertCount(1, $this->resolver->getConfig()->getFinder());
+    }
+
+    public function testResolvePathWithFileThatIsExcludedByDirIntersectionPathMode()
     {
         $dir = dirname(__DIR__);
         $this->config->getFinder()
@@ -299,6 +327,7 @@ final class ConfigurationResolverTest extends \PHPUnit_Framework_TestCase
 
         $this->resolver
             ->setOption('path', array(__FILE__))
+            ->setOption('path-mode', 'intersection')
             ->resolve();
 
         $this->assertCount(0, $this->resolver->getConfig()->getFinder());
@@ -321,15 +350,23 @@ final class ConfigurationResolverTest extends \PHPUnit_Framework_TestCase
     /**
      * @dataProvider provideResolveIntersectionOfPathsCases
      */
-    public function testResolveIntersectionOfPaths($expectedIntersectionItems, $configFinder, array $path, $config = null)
+    public function testResolveIntersectionOfPaths($expected, $configFinder, array $path, $pathMode, $config = null)
     {
-        $this->config->finder($configFinder);
+        if (null !== $configFinder) {
+            $this->config->finder($configFinder);
+        }
+
         $this->resolver
             ->setOption('path', $path)
+            ->setOption('path-mode', $pathMode)
             ->setOption('config', $config)
             ->resolve()
         ;
 
+        if ($expected instanceof \Exception) {
+            $this->setExpectedException(get_class($expected));
+        }
+
         $intersectionItems = array_map(
             function (\SplFileInfo $file) {
                 return $file->getRealPath();
@@ -337,10 +374,10 @@ final class ConfigurationResolverTest extends \PHPUnit_Framework_TestCase
             iterator_to_array($this->resolver->getConfig()->getFinder(), false)
         );
 
-        sort($expectedIntersectionItems);
+        sort($expected);
         sort($intersectionItems);
 
-        $this->assertSame($expectedIntersectionItems, $intersectionItems);
+        $this->assertSame($expected, $intersectionItems);
     }
 
     public function provideResolveIntersectionOfPathsCases()
@@ -356,85 +393,111 @@ final class ConfigurationResolverTest extends \PHPUnit_Framework_TestCase
         };
 
         return array(
-            // configured only by finder
-            array(
+            'no path at all' => array(
+                new \LogicException(),
+                Finder::create(),
+                array(),
+                'override',
+            ),
+            'configured only by finder' => array(
+                // don't override if the argument is empty
                 $cb(array('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')),
                 Finder::create()
                     ->in($dir),
                 array(),
+                'override',
             ),
-            // configured only by argument
-            array(
+            'configured only by argument' => array(
                 $cb(array('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')),
                 Finder::create(),
                 array($dir),
+                'override',
             ),
-            // configured by finder, filtered by argument which is dir
-            array(
+            'configured by finder, intersected with empty argument' => array(
+                array(),
+                Finder::create()
+                    ->in($dir),
+                array(),
+                'intersection',
+            ),
+            'configured by finder, intersected with dir' => array(
                 $cb(array('c/c1.php', 'c/d/cd1.php')),
                 Finder::create()
                     ->in($dir),
                 array($dir.'/c'),
+                'intersection',
             ),
-            // configured by finder, filtered by argument which is file
-            array(
+            'configured by finder, intersected with file' => array(
                 $cb(array('c/c1.php')),
                 Finder::create()
                     ->in($dir),
                 array($dir.'/c/c1.php'),
+                'intersection',
             ),
-            // finder points to one dir while argument to another, not connected
-            array(
+            'finder points to one dir while argument to another, not connected' => array(
                 array(),
                 Finder::create()
                     ->in($dir.'/b'),
                 array($dir.'/c'),
+                'intersection',
             ),
-            // finder with excluded dir, argument points to excluded file
-            array(
+            'finder with excluded dir, intersected with excluded file' => array(
                 array(),
                 Finder::create()
                     ->in($dir)
                     ->exclude('c'),
                 array($dir.'/c/d/cd1.php'),
+                'intersection',
             ),
-            // finder with excluded dir, argument points to excluded parent
-            array(
+            'finder with excluded dir, intersected with dir containing excluded one' => array(
                 $cb(array('c/c1.php')),
                 Finder::create()
                     ->in($dir)
                     ->exclude('c/d'),
                 array($dir.'/c'),
+                'intersection',
             ),
-            // finder with excluded file, argument points to excluded parent
-            array(
+            'finder with excluded file, intersected with dir containing excluded one' => array(
                 $cb(array('c/d/cd1.php')),
                 Finder::create()
                     ->in($dir)
                     ->notPath('c/c1.php'),
                 array($dir.'/c'),
+                'intersection',
             ),
-            // configured by finder, argument points to not-existing path
-            array(
+            'configured by finder, intersected with non-existing path' => array(
                 array(),
                 Finder::create()
                     ->in($dir),
                 array('non_existing_dir'),
+                'intersection',
             ),
-            // configured by finder, filtered by argument which is multiple files
-            array(
+            'configured by finder, overriden by multiple files' => array(
                 $cb(array('d/d1.php', 'd/d2.php')),
-                Finder::create()
-                    ->in($dir),
+                null,
                 array($dir.'/d/d1.php', $dir.'/d/d2.php'),
+                'override',
                 $dir.'/d/.php_cs',
             ),
-            // configured by finder, filtered by argument which is multiple files and dirs
-            array(
+            'configured by finder, intersected with multiple files' => array(
+                $cb(array('d/d1.php', 'd/d2.php')),
+                null,
+                array($dir.'/d/d1.php', $dir.'/d/d2.php'),
+                'intersection',
+                $dir.'/d/.php_cs',
+            ),
+            'configured by finder, overriden by multiple files and dirs' => array(
                 $cb(array('d/d1.php', 'd/e/de1.php', 'd/f/df1.php')),
-                Finder::create()
-                    ->in($dir),
+                null,
+                array($dir.'/d/d1.php', $dir.'/d/e', $dir.'/d/f/'),
+                'override',
+                $dir.'/d/.php_cs',
+            ),
+            'configured by finder, intersected with multiple files and dirs' => array(
+                $cb(array('d/d1.php', 'd/e/de1.php', 'd/f/df1.php')),
+                null,
                 array($dir.'/d/d1.php', $dir.'/d/e', $dir.'/d/f/'),
+                'intersection',
                 $dir.'/d/.php_cs',
             ),
         );

+ 19 - 2
tests/Fixtures/ConfigurationResolverPathsIntersection/d/.php_cs

@@ -1,7 +1,24 @@
 <?php
 
+/*
+ * This file is part of PHP CS Fixer.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *     Dariusz Rumiński <dariusz.ruminski@gmail.com>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
 if (!class_exists('ConfigurationResolverPathsIntersection_d_Config')) {
-    class ConfigurationResolverPathsIntersection_d_Config extends PhpCsFixer\Config {}
+    class ConfigurationResolverPathsIntersection_d_Config extends PhpCsFixer\Config
+    {
+    }
 }
 
-return ConfigurationResolverPathsIntersection_d_Config::create();
+return ConfigurationResolverPathsIntersection_d_Config::create()
+    ->finder(
+        PhpCsFixer\Finder::create()
+            ->in(__DIR__.'/..')
+    )
+;