diff --git a/src/Util/Path.php b/src/Util/Path.php index 088d9121..c6508b4d 100644 --- a/src/Util/Path.php +++ b/src/Util/Path.php @@ -95,7 +95,7 @@ public static function isAnalysableFile(string $path, string $basePath): bool */ public static function relativeTo(string $path, string $normalisedBasePath): string { - $normalisedPath = self::normalise($path, canonicalise: true); + $normalisedPath = self::normalise(self::resolve($path, $normalisedBasePath), canonicalise: true); if ($normalisedPath === $normalisedBasePath) { return ''; diff --git a/tests/Cli/StructArmedApplicationTest.php b/tests/Cli/StructArmedApplicationTest.php index 96e900e4..0c39b0ee 100644 --- a/tests/Cli/StructArmedApplicationTest.php +++ b/tests/Cli/StructArmedApplicationTest.php @@ -19,9 +19,11 @@ use PHPUnit\Framework\TestCase; use function bin2hex; +use function chdir; use function file_exists; use function file_get_contents; use function file_put_contents; +use function getcwd; use function glob; use function is_dir; use function json_decode; @@ -1448,6 +1450,91 @@ public function testAnalyseCommandGeneratesBaseline(): void } } + /** + * A baseline entry stores its file relative to the base path. Running from + * a monorepo root that holds a file at the same relative path must not + * resolve the entry to that file instead. + */ + public function testAnalyseCommandAppliesBaselineFromOtherWorkingDirectoryWithBasePathOption(): void + { + $rootPath = (string) realpath($this->createTempDirectory()); + $packagePath = $rootPath . '/packages/app'; + + mkdir($rootPath . '/src'); + mkdir($packagePath . '/src', 0777, true); + + $source = <<<'PHP' +layer('Source', 'src/') + ->rule('source.must_be_final', new MustBeFinalRule('Source')) + ->baseline('structarmed-baseline.php'); +PHP); + + $originalWorkingDirectory = getcwd(); + $this->assertNotFalse($originalWorkingDirectory); + + try { + [$generateExitCode, $generateOutput] = $this->runApplication( + [ + 'structarmed', + 'analyse', + '--no-progress', + '--no-cache', + '--generate-baseline=structarmed-baseline.php', + ], + $packagePath + ); + + $this->assertSame(0, $generateExitCode, $generateOutput); + $this->assertStringContainsString( + "'file' => 'src/Foo.php'", + (string) file_get_contents($packagePath . '/structarmed-baseline.php') + ); + + chdir($rootPath); + + [$exitCode, $output] = $this->runApplication( + [ + 'structarmed', + 'analyse', + '--basepath=packages/app', + '--config=packages/app/structarmed.php', + '--no-progress', + '--no-cache', + ], + $rootPath + ); + + $this->assertSame(0, $exitCode, $output); + $this->assertStringContainsString('No violations found.', $output); + } finally { + chdir($originalWorkingDirectory); + $this->removeTempDirectory($packagePath); + unlink($rootPath . '/src/Foo.php'); + rmdir($rootPath . '/src'); + rmdir($rootPath . '/packages'); + rmdir($rootPath); + } + } + public function testAnalyseCommandGeneratesBaselineWithSeparateOptionValue(): void { $basePath = $this->createProjectDirectoryWithViolation(); diff --git a/tests/Util/PathTest.php b/tests/Util/PathTest.php index 21926b7c..8090832c 100644 --- a/tests/Util/PathTest.php +++ b/tests/Util/PathTest.php @@ -92,6 +92,9 @@ public static function provideRelativeTo(): Iterator yield 'base itself' => ['/project', '/project', '']; yield 'sibling of base' => ['/shared/src/Foo.php', '/project', '../shared/src/Foo.php']; yield 'no common ancestor' => ['C:/other/Foo.php', '/project', 'C:/other/Foo.php']; + // resolved against the base path, never the working directory + yield 'relative to base' => ['src/Foo.php', '/project', 'src/Foo.php']; + yield 'empty is the base' => ['', '/project', '']; } #[DataProvider('provideRelativeTo')]