Repository: VincentLanglet/Twig-CS-Fixer (php)

Issue:
Allow realpath on each filenames for `checkstyle` and `junit` reports

Hello,

IMO, it's always difficult to re-use reports generated (whatever format) with relative filenames, on third-party tool.

As author of package https://github.com/llaville/sarif-php-converters (still under development), 
I've a `Console Tool` that is able to convert a report from `checkstyle` format to `sarif`.
But it's almost impossible if source provider did not send the absolute path on each files.


When we lint a source (e.g: Twig-CS-Fixer repo itself)

```shell
devilbox@php-8.2.20 in /shared/backups/forks/Twig-CS-Fixer $ bin/twig-cs-fixer lint --report checkstyle
```

We got such kind of report

```xml
<?xml version="1.0" encoding="UTF-8"?>
<checkstyle>
  <file name="./tests/Runner/Fixtures/Linter/node_visitor.twig">
    <error line="5" column="16" severity="error" message="Expecting 0 whitespace after &quot;|&quot;; found 1." source="TwigCsFixer\Rules\Punctuation\PunctuationSpacingRule"/>
    <error line="5" column="16" severity="error" message="Expecting 0 whitespace before &quot;|&quot;; found 1." source="TwigCsFixer\Rules\Punctuation\PunctuationSpacingRule"/>
    <error line="13" column="20" severity="error" message="Expecting 0 whitespace after &quot;|&quot;; found 1." source="TwigCsFixer\Rules\Punctuation\PunctuationSpacingRule"/>
    <error line="13" column="20" severity="error" message="Expecting 0 whitespace before &quot;|&quot;; found 1." source="TwigCsFixer\Rules\Punctuation\PunctuationSpacingRule"/>
    <error line="4" column="1" severity="error" message="More than 1 empty line is not allowed, found 3" source="TwigCsFixer\Rules\Whitespace\EmptyLinesRule"/>
    <error line="8" column="1" severity="error" message="More than 1 empty line is not allowed, found 3" source="TwigCsFixer\Rules\Whitespace\EmptyLinesRule"/>
    <error line="12" column="1" severity="error" message="More than 1 empty line is not allowed, found 3" source="TwigCsFixer\Rules\Whitespace\EmptyLinesRule"/>
    <error line="16" column="1" severity="error" message="More than 1 empty line is not allowed, found 3" source="TwigCsFixer\Rules\Whitespace\EmptyLinesRule"/>
    <error line="20" column="1" severity="error" message="More than 1 empty line is not allowed, found 3" source="TwigCsFixer\Rules\Whitespace\EmptyLinesRule"/>
    <error line="24" column="1" severity="error" message="More than 1 empty line is not allowed, found 3" source="TwigCsFixer\Rules\Whitespace\EmptyLinesRule"/>
    <error line="28" column="1" severity="error" message="More than 1 empty line is not allowed, found 3" source="TwigCsFixer\Rules\Whitespace\EmptyLinesRule"/>
  </file>

  <...MORE...>

</checkstyle>
```

> [!WARNING]
>
> Look deep on file names (relative paths)

It may be solved quickly, if we specify the absolute path to source to analyse

```shell
devilbox@php-8.2.20 in /shared/backups/forks/Twig-CS-Fixer $ bin/twig-cs-fixer lint --report checkstyle /shared/backups/forks/Twig-CS-Fixer
```

```xml
<?xml version="1.0" encoding="UTF-8"?>
<checkstyle>
  <file name="/shared/backups/forks/Twig-CS-Fixer/tests/Runner/Fixtures/Linter/node_visitor.twig">

```

But there is also an alternative to fix it by PR.

Are you agree with such request ?


Pull request description:
Uses absolute path on checkstyle and junit report only
fixes #261

Patch:
diff --git a/SECURITY.md b/SECURITY.md
new file mode 100644
index 0000000..801d26b
--- /dev/null
+++ b/SECURITY.md
@@ -0,0 +1,18 @@
+# Security Policy
+
+## Supported Versions
+
+Only the latest major version is supported with security updates.
+
+| Version | Supported          |
+| ------- | ------------------ |
+| 2.x     | :white_check_mark: |
+| 1.x     | :x:                |
+
+## Reporting a Vulnerability
+
+To report a vulnerability, please follow [https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing/privately-reporting-a-security-vulnerability](https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing-information-about-vulnerabilities/privately-reporting-a-security-vulnerability#privately-reporting-a-security-vulnerability)
+
+Once your report is received, the project maintainers will review it and respond accordingly. We appreciate your responsible disclosure and will make every effort to address the issue in a timely manner.
+
+Thank you for helping us maintain the security of Twig-CS-Fixer!
diff --git a/src/Report/Report.php b/src/Report/Report.php
index 12ab79b..4d56b2f 100644
--- a/src/Report/Report.php
+++ b/src/Report/Report.php
@@ -30,13 +30,22 @@ final class Report
 
     private int $totalErrors = 0;
 
+    /**
+     * @var array<string, string>
+     */
+    private array $realPaths = [];
+
     /**
      * @param iterable<\SplFileInfo> $files
      */
     public function __construct(iterable $files)
     {
         foreach ($files as $file) {
-            $this->violationsByFile[$file->getPathname()] = [];
+            $pathName = $file->getPathname();
+            $realPath = $file->getRealPath();
+
+            $this->realPaths[$pathName] = false !== $realPath ? $realPath : $pathName;
+            $this->violationsByFile[$pathName] = [];
         }
     }
 
@@ -118,6 +127,17 @@ final class Report
         return array_keys($this->violationsByFile);
     }
 
+    public function getRealPath(string $filename): string
+    {
+        if (!isset($this->realPaths[$filename])) {
+            throw new \InvalidArgumentException(
+                sprintf('The file "%s" is not handled by this report.', $filename)
+            );
+        }
+
+        return $this->realPaths[$filename];
+    }
+
     public function getTotalFiles(): int
     {
         return \count($this->violationsByFile);
diff --git a/src/Report/Reporter/CheckstyleReporter.php b/src/Report/Reporter/CheckstyleReporter.php
index f7f01f6..5739ea7 100644
--- a/src/Report/Reporter/CheckstyleReporter.php
+++ b/src/Report/Reporter/CheckstyleReporter.php
@@ -28,7 +28,8 @@ final class CheckstyleReporter implements ReporterInterface
                 continue;
             }
 
-            $text .= sprintf('  <file name="%s">', $this->xmlEncode($file)).\PHP_EOL;
+            $realPath = $report->getRealPath($file);
+            $text .= sprintf('  <file name="%s">', $this->xmlEncode($realPath)).\PHP_EOL;
             foreach ($fileViolations as $violation) {
                 $line = (string) $violation->getLine();
                 $linePosition = (string) $violation->getLinePosition();
diff --git a/src/Report/Reporter/JUnitReporter.php b/src/Report/Reporter/JUnitReporter.php
index 1b52f9a..5172c0d 100644
--- a/src/Report/Reporter/JUnitReporter.php
+++ b/src/Report/Reporter/JUnitReporter.php
@@ -32,7 +32,7 @@ final class JUnitReporter implements ReporterInterface
         if ($count > 0) {
             foreach ($violations as $violation) {
                 $text .= $this->createTestCase(
-                    sprintf('%s:%s', $violation->getFilename(), $violation->getLine() ?? 0),
+                    sprintf('%s:%s', $report->getRealPath($violation->getFilename()), $violation->getLine() ?? 0),
                     strtolower(Violation::getLevelAsString($violation->getLevel())),
                     $violation->getDebugMessage($debug)
                 );


Test patch:
diff --git a/tests/Report/Reporter/CheckstyleReporterTest.php b/tests/Report/Reporter/CheckstyleReporterTest.php
index 9ddb9c8..ecef7c1 100644
--- a/tests/Report/Reporter/CheckstyleReporterTest.php
+++ b/tests/Report/Reporter/CheckstyleReporterTest.php
@@ -22,9 +22,10 @@ final class CheckstyleReporterTest extends TestCase
     {
         $textFormatter = new CheckstyleReporter();
 
-        $file = TestHelper::getOsPath(__DIR__.'/Fixtures/file.twig');
+        // Use a mix of relative and absolute path.
+        $file = TestHelper::getOsPath('tests/Report/Reporter/Fixtures/file.twig');
         $file2 = TestHelper::getOsPath(__DIR__.'/Fixtures/file2.twig');
-        $file3 = TestHelper::getOsPath(__DIR__.'/Fixtures/file3.twig');
+        $file3 = TestHelper::getOsPath('tests/Report/Reporter/Fixtures/file3.twig'); // Non-existing file
         $report = new Report([new \SplFileInfo($file), new \SplFileInfo($file2), new \SplFileInfo($file3)]);
 
         $violation0 = new Violation(
@@ -111,7 +112,7 @@ final class CheckstyleReporterTest extends TestCase
                     EOD,
                 TestHelper::getOsPath(__DIR__.'/Fixtures/file.twig'),
                 TestHelper::getOsPath(__DIR__.'/Fixtures/file2.twig'),
-                TestHelper::getOsPath(__DIR__.'/Fixtures/file3.twig'),
+                TestHelper::getOsPath('tests/Report/Reporter/Fixtures/file3.twig'),
             ),
             null,
             false,
@@ -132,7 +133,7 @@ final class CheckstyleReporterTest extends TestCase
                     </checkstyle>
                     EOD,
                 TestHelper::getOsPath(__DIR__.'/Fixtures/file.twig'),
-                TestHelper::getOsPath(__DIR__.'/Fixtures/file3.twig'),
+                TestHelper::getOsPath('tests/Report/Reporter/Fixtures/file3.twig'),
             ),
             Report::MESSAGE_TYPE_ERROR,
             false,
@@ -153,7 +154,7 @@ final class CheckstyleReporterTest extends TestCase
                     </checkstyle>
                     EOD,
                 TestHelper::getOsPath(__DIR__.'/Fixtures/file.twig'),
-                TestHelper::getOsPath(__DIR__.'/Fixtures/file3.twig'),
+                TestHelper::getOsPath('tests/Report/Reporter/Fixtures/file3.twig'),
             ),
             Report::MESSAGE_TYPE_ERROR,
             true,
diff --git a/tests/Report/Reporter/GithubReporterTest.php b/tests/Report/Reporter/GithubReporterTest.php
index 9fde2aa..3e9b7ec 100644
--- a/tests/Report/Reporter/GithubReporterTest.php
+++ b/tests/Report/Reporter/GithubReporterTest.php
@@ -22,7 +22,7 @@ final class GithubReporterTest extends TestCase
     {
         $textFormatter = new GithubReporter();
 
-        $file = TestHelper::getOsPath(__DIR__.'/Fixtures/file.twig');
+        $file = TestHelper::getOsPath('tests/Report/Reporter/Fixtures/file.twig');
         $report = new Report([new \SplFileInfo($file)]);
 
         $violation0 = new Violation(
@@ -78,7 +78,7 @@ final class GithubReporterTest extends TestCase
                     ::error file=%1\$s,line=3,col=33::Error
                     ::error file=%1\$s::Fatal%%0Awith new line
                     EOD,
-                TestHelper::getOsPath(__DIR__.'/Fixtures/file.twig'),
+                TestHelper::getOsPath('tests/Report/Reporter/Fixtures/file.twig'),
             ),
             null,
             false,
@@ -91,7 +91,7 @@ final class GithubReporterTest extends TestCase
                     ::error file=%1\$s,line=3,col=33::ErrorId:3:33
                     ::error file=%1\$s::FatalId
                     EOD,
-                TestHelper::getOsPath(__DIR__.'/Fixtures/file.twig'),
+                TestHelper::getOsPath('tests/Report/Reporter/Fixtures/file.twig'),
             ),
             null,
             true,
diff --git a/tests/Report/Reporter/JUnitReporterTest.php b/tests/Report/Reporter/JUnitReporterTest.php
index c5b0fe2..68bf7ec 100644
--- a/tests/Report/Reporter/JUnitReporterTest.php
+++ b/tests/Report/Reporter/JUnitReporterTest.php
@@ -22,9 +22,10 @@ final class JUnitReporterTest extends TestCase
     {
         $textFormatter = new JUnitReporter();
 
-        $file = TestHelper::getOsPath(__DIR__.'/Fixtures/file.twig');
+        // Use a mix of relative and absolute path.
+        $file = TestHelper::getOsPath('tests/Report/Reporter/Fixtures/file.twig');
         $file2 = TestHelper::getOsPath(__DIR__.'/Fixtures/file2.twig');
-        $file3 = TestHelper::getOsPath(__DIR__.'/Fixtures/file3.twig');
+        $file3 = TestHelper::getOsPath('tests/Report/Reporter/Fixtures/file3.twig'); // Non-existing file
         $report = new Report([new \SplFileInfo($file), new \SplFileInfo($file2), new \SplFileInfo($file3)]);
 
         $violation0 = new Violation(
@@ -119,7 +120,7 @@ final class JUnitReporterTest extends TestCase
                     EOD,
                 TestHelper::getOsPath(__DIR__.'/Fixtures/file.twig'),
                 TestHelper::getOsPath(__DIR__.'/Fixtures/file2.twig'),
-                TestHelper::getOsPath(__DIR__.'/Fixtures/file3.twig'),
+                TestHelper::getOsPath('tests/Report/Reporter/Fixtures/file3.twig'),
             ),
             null,
             false,
@@ -153,7 +154,7 @@ final class JUnitReporterTest extends TestCase
                     EOD,
                 TestHelper::getOsPath(__DIR__.'/Fixtures/file.twig'),
                 TestHelper::getOsPath(__DIR__.'/Fixtures/file2.twig'),
-                TestHelper::getOsPath(__DIR__.'/Fixtures/file3.twig'),
+                TestHelper::getOsPath('tests/Report/Reporter/Fixtures/file3.twig'),
             ),
             null,
             true,
diff --git a/tests/Report/Reporter/TextReporterTest.php b/tests/Report/Reporter/TextReporterTest.php
index 05b9657..8109cd9 100644
--- a/tests/Report/Reporter/TextReporterTest.php
+++ b/tests/Report/Reporter/TextReporterTest.php
@@ -22,7 +22,7 @@ final class TextReporterTest extends TestCase
     {
         $textFormatter = new TextReporter();
 
-        $file = TestHelper::getOsPath(__DIR__.'/Fixtures/file.twig');
+        $file = TestHelper::getOsPath('tests/Report/Reporter/Fixtures/file.twig');
         $report = new Report([new \SplFileInfo($file)]);
 
         $violation0 = new Violation(
@@ -93,7 +93,7 @@ final class TextReporterTest extends TestCase
                       \e[33mFATAL\e[39m     \e[31m>>   | Fatal\e[39m                           
                      --------- --------------------------------------- 
                     EOD,
-                TestHelper::getOsPath(__DIR__.'/Fixtures/file.twig')
+                TestHelper::getOsPath('tests/Report/Reporter/Fixtures/file.twig')
             ),
             null,
             false,
@@ -112,7 +112,7 @@ final class TextReporterTest extends TestCase
                       \e[33mFATAL\e[39m   \e[31m>>   | Fatal\e[39m                       
                      ------- ----------------------------------- 
                     EOD,
-                TestHelper::getOsPath(__DIR__.'/Fixtures/file.twig')
+                TestHelper::getOsPath('tests/Report/Reporter/Fixtures/file.twig')
             ),
             Report::MESSAGE_TYPE_ERROR,
             false,
@@ -131,7 +131,7 @@ final class TextReporterTest extends TestCase
                       \e[33mFATAL\e[39m   \e[31m>>   | FatalId\e[39m                     
                      ------- ----------------------------------- 
                     EOD,
-                TestHelper::getOsPath(__DIR__.'/Fixtures/file.twig')
+                TestHelper::getOsPath('tests/Report/Reporter/Fixtures/file.twig')
             ),
             Report::MESSAGE_TYPE_ERROR,
             true,