Repository: PHP-CS-Fixer/PHP-CS-Fixer (PHP)

Issue:
Replacing CWD in list-files command is too agressive
## Bug report

### Description

When using `list-files` command, all occurrences of `cwd` are replaced in file paths, but this is not checked the right way.

Example:
CWD: /opt
File: ./fixtures/opt_test.php
Result: ./fixtures._test.php

which is producing an error since this file doesn't exist. All this is caused by line 86 in ListFilesCommand.php

### Runtime version

PHP CS Fixer 3.15.1 BoY42 by Fabien Potencier and Dariusz Ruminski.
PHP runtime: 8.2.3

### Used command

vendor/bin/php-cs-fixer list-files --config=.php-cs-fixer.dist.php 

### Configuration file

```
<?php

$finder = PhpCsFixer\Finder::create()
    ->in(__DIR__ . '/fixtures')
    ->in(__DIR__ . '/src')
    ->in(__DIR__ . '/tests')
    ->in(__DIR__ . '/config')
;

$config = new PhpCsFixer\Config();
return $config
    ->registerCustomFixers(new PhpCsFixerCustomFixers\Fixers())
    ->setRules([
        '@Symfony' => true,
        '@Symfony:risky' => true,
        'declare_strict_types' => true,
        'yoda_style' => false,
        'phpdoc_align' => false,
        'phpdoc_separation' => false,
        'array_syntax' => ['syntax' => 'short'],
        'ordered_imports' => [
            'imports_order' => ['class', 'function', 'const'],
        ],
        'trailing_comma_in_multiline' => [
            'elements' => ['arrays', 'arguments', 'parameters'],
        ],
        'method_argument_space' => [
            'on_multiline' => 'ensure_fully_multiline',
        ],
        'fopen_flags' => [
            'b_mode' => true,
        ],
        'no_unneeded_final_method' => false,  // conflicts with PHPStan
        'single_line_throw' => false, // broken with multiple throws in match cases
        'no_break_comment' => false,
        'native_function_invocation' => [
            'include' => [],
        ],
        'array_indentation' => true,
        PhpCsFixerCustomFixers\Fixer\MultilinePromotedPropertiesFixer::name() => true,
        PhpCsFixerCustomFixers\Fixer\NoTrailingCommaInSinglelineFixer::name() => true,
        PhpCsFixerCustomFixers\Fixer\PhpUnitAssertArgumentsOrderFixer::name() => true,
        PhpCsFixerCustomFixers\Fixer\PhpUnitDedicatedAssertFixer::name() => true,
        PhpCsFixerCustomFixers\Fixer\NoImportFromGlobalNamespaceFixer::name() => true,
])
    ->setFinder($finder)
;
```

### Code snippet that reproduces the problem

No code, since the issue is not caused by the code.



Discussion:
@kdrwila can you reproduce it with latest 3.17 version?
Yes, I've checked the github source code if the line was changed in newer version before writing this issue. 

Problem still persists on 3.17, here is the output:

```
'./fixtures/product/brands/advertisement_display_link.php'
'./fixtures/product/leads._out.php'
'./fixtures/product/leads/lead_status_list.php'
```

when the file name is opt_out.php



Patch:
diff --git a/src/Console/Command/ListFilesCommand.php b/src/Console/Command/ListFilesCommand.php
index f714131403d..b036fbac43e 100644
--- a/src/Console/Command/ListFilesCommand.php
+++ b/src/Console/Command/ListFilesCommand.php
@@ -23,6 +23,7 @@
 use Symfony\Component\Console\Input\InputInterface;
 use Symfony\Component\Console\Input\InputOption;
 use Symfony\Component\Console\Output\OutputInterface;
+use Symfony\Component\Filesystem\Path;
 
 /**
  * @author Markus Staab <markus.staab@redaxo.org>
@@ -80,7 +81,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int
         /** @var \SplFileInfo $file */
         foreach ($finder as $file) {
             if ($file->isFile()) {
-                $relativePath = str_replace($cwd, '.', $file->getRealPath());
+                $relativePath = './'.Path::makeRelative($file->getRealPath(), $cwd);
                 // unify directory separators across operating system
                 $relativePath = str_replace('/', \DIRECTORY_SEPARATOR, $relativePath);
 


Test patch:
diff --git a/tests/Console/Command/ListFilesCommandTest.php b/tests/Console/Command/ListFilesCommandTest.php
index baf077487fd..6d15f82c220 100644
--- a/tests/Console/Command/ListFilesCommandTest.php
+++ b/tests/Console/Command/ListFilesCommandTest.php
@@ -19,6 +19,8 @@
 use PhpCsFixer\Tests\TestCase;
 use PhpCsFixer\ToolInfo;
 use Symfony\Component\Console\Tester\CommandTester;
+use Symfony\Component\Filesystem\Filesystem;
+use Symfony\Component\Filesystem\Path;
 
 /**
  * @internal
@@ -27,6 +29,18 @@
  */
 final class ListFilesCommandTest extends TestCase
 {
+    private static ?Filesystem $filesystem;
+
+    public static function setUpBeforeClass(): void
+    {
+        self::$filesystem = new Filesystem();
+    }
+
+    public static function tearDownAfterClass(): void
+    {
+        self::$filesystem = null;
+    }
+
     public function testListWithConfig(): void
     {
         $commandTester = $this->doTestExecute([
@@ -41,6 +55,32 @@ public function testListWithConfig(): void
         self::assertSame(escapeshellarg($expectedPath).PHP_EOL, $commandTester->getDisplay());
     }
 
+    /**
+     * @requires OS Linux|Darwin
+     *
+     * Skip test on Windows as `getcwd()` includes the drive letter with a colon `:` which is illegal in filenames.
+     */
+    public function testListFilesDoesNotCorruptListWithGetcwdInName(): void
+    {
+        try {
+            $tmpDir = __DIR__.'/../../Fixtures/ListFilesTest/using-getcwd';
+            $tmpFile = $tmpDir.'/'.ltrim(getcwd(), '/').'-out.php';
+            self::$filesystem->dumpFile($tmpFile, '<?php function a() {  }');
+
+            $tmpFile = realpath($tmpFile);
+            self::assertFileExists($tmpFile);
+
+            $commandTester = $this->doTestExecute([
+                '--config' => __DIR__.'/../../Fixtures/ListFilesTest/.php-cs-fixer.using-getcwd.php',
+            ]);
+            $expectedPath = str_replace('/', \DIRECTORY_SEPARATOR, './'.Path::makeRelative($tmpFile, getcwd()));
+            self::assertSame(0, $commandTester->getStatusCode());
+            self::assertSame(escapeshellarg($expectedPath).PHP_EOL, $commandTester->getDisplay());
+        } finally {
+            self::$filesystem->remove($tmpDir);
+        }
+    }
+
     /**
      * @param array<string, bool|string> $arguments
      */
diff --git a/tests/Fixtures/ListFilesTest/.php-cs-fixer.using-getcwd.php b/tests/Fixtures/ListFilesTest/.php-cs-fixer.using-getcwd.php
new file mode 100644
index 00000000000..f6ca0d45c0a
--- /dev/null
+++ b/tests/Fixtures/ListFilesTest/.php-cs-fixer.using-getcwd.php
@@ -0,0 +1,8 @@
+<?php
+
+/** @var PhpCsFixer\Config $config */
+$config = require __DIR__.'/.php-cs-fixer.php';
+
+return $config
+    ->setFinder((new PhpCsFixer\Finder())->in([__DIR__.'/using-getcwd']))
+;