diff --git a/.gitignore b/.gitignore index cd51ed9..1f1aa01 100644 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,7 @@ composer.phar .phpunit.cache /tests/.results/ .DS_Store -*.local \ No newline at end of file +*.local + +.claude +.cursor diff --git a/Kununu/CsFixer/Command/CsFixerGitHookCommand.php b/Kununu/CsFixer/Command/CsFixerGitHookCommand.php index 6d2b92c..2ef8db3 100644 --- a/Kununu/CsFixer/Command/CsFixerGitHookCommand.php +++ b/Kununu/CsFixer/Command/CsFixerGitHookCommand.php @@ -80,21 +80,21 @@ private function installHook(string $gitPath): void $sourceHook = __DIR__ . '/../Hooks/git-pre-commit'; $destHook = $hooksDir . '/pre-commit'; - if (!is_dir($hooksDir) && !mkdir($hooksDir, 0777, true) && !is_dir($hooksDir)) { + if (!is_dir($hooksDir) && !@mkdir($hooksDir, 0777, true) && !is_dir($hooksDir)) { throw new RuntimeException(sprintf( 'Could not create hooks directory: "%s".', $hooksDir )); } - if (file_exists($destHook) && !unlink($destHook)) { + if (file_exists($destHook) && !@unlink($destHook)) { throw new RuntimeException(sprintf( 'Could not remove existing hook at "%s".', $destHook )); } - if (!copy($sourceHook, $destHook)) { + if (!@copy($sourceHook, $destHook)) { throw new RuntimeException(sprintf( 'Failed to copy hook from "%s" to "%s".', $sourceHook, @@ -102,7 +102,7 @@ private function installHook(string $gitPath): void )); } - if (!chmod($destHook, 0755)) { + if (!@chmod($destHook, 0755)) { throw new RuntimeException(sprintf( 'Failed to make hook executable at "%s".', $destHook @@ -151,7 +151,7 @@ private function ensureSymlinkRelative(string $target, string $linkPath): void { $linkDir = dirname($linkPath); - if (!is_dir($linkDir) && !mkdir($linkDir, 0777, true) && !is_dir($linkDir)) { + if (!is_dir($linkDir) && !@mkdir($linkDir, 0777, true) && !is_dir($linkDir)) { throw new RuntimeException(sprintf( 'Could not create directory for symlink: "%s".', $linkDir @@ -164,7 +164,7 @@ private function ensureSymlinkRelative(string $target, string $linkPath): void $relativeTarget = $this->makeRelativePath($linkDir, $target); - if (!symlink($relativeTarget, $linkPath)) { + if (!@symlink($relativeTarget, $linkPath)) { throw new RuntimeException(sprintf( 'Failed to create symlink from "%s" to "%s".', $linkPath, diff --git a/tests/Unit/Kununu/CsFixer/Command/CsFixerCommandTest.php b/tests/Unit/Kununu/CsFixer/Command/CsFixerCommandTest.php index c947139..9ff7456 100644 --- a/tests/Unit/Kununu/CsFixer/Command/CsFixerCommandTest.php +++ b/tests/Unit/Kununu/CsFixer/Command/CsFixerCommandTest.php @@ -121,6 +121,28 @@ public function testCsFixerCommandReturnsFailureWhenConfigNotFound(): void self::assertSame(CsFixerCommand::FAILURE, $exitCode); } + public function testCsFixerCommandReturnsFailureWhenProcessFails(): void + { + $this->tempFile = sys_get_temp_dir() . '/csfixer_' . uniqid('', true) . '.php'; + file_put_contents($this->tempFile, "addCommand($command) + : $application->add($command); + + $command = $application->find('kununu:cs-fixer'); + $tester = new CommandTester($command); + + $exitCode = $tester->execute([ + 'files' => [$this->tempFile], + '--extra-args' => ['--invalid-flag-xyz'], + ]); + + self::assertSame(CsFixerCommand::FAILURE, $exitCode); + } + protected function tearDown(): void { if ($this->tempFile !== null && is_file($this->tempFile)) { diff --git a/tests/Unit/Kununu/CsFixer/Command/CsFixerGitHookCommandTest.php b/tests/Unit/Kununu/CsFixer/Command/CsFixerGitHookCommandTest.php index 9a2b069..53930c4 100644 --- a/tests/Unit/Kununu/CsFixer/Command/CsFixerGitHookCommandTest.php +++ b/tests/Unit/Kununu/CsFixer/Command/CsFixerGitHookCommandTest.php @@ -79,6 +79,126 @@ public function testInstallsHookSuccessfully(): void self::assertTrue(is_link($symlinkBin), 'php-cs-fixer symlink should exist'); } + public function testReinstallRemovesExistingHookAndSymlinks(): void + { + chdir($this->repoDir); + exec('git init 2>/dev/null'); + + $vendorBase = $this->baseDir . '/vendor'; + $codeToolsDir = $vendorBase . '/kununu/code-tools'; + $binDir = $vendorBase . '/bin'; + mkdir($codeToolsDir, 0777, true); + mkdir($binDir, 0777, true); + file_put_contents($codeToolsDir . '/php-cs-fixer.php', "addCommand($command) + : $app->add($command); + + $command = $app->find('kununu:cs-fixer-git-hook'); + $tester = new CommandTester($command); + + $tester->execute([]); + + $exitCode = $tester->execute([]); + + self::assertSame(CsFixerGitHookCommand::SUCCESS, $exitCode); + self::assertFileExists($this->repoDir . '/.git/hooks/pre-commit'); + self::assertTrue(is_link($this->repoDir . '/.git/kununu/.php-cs-fixer.php')); + self::assertTrue(is_link($this->repoDir . '/.git/kununu/php-cs-fixer')); + } + + public function testFailsWhenGitPathIsNotDirectory(): void + { + $mainRepo = $this->baseDir . '/main'; + mkdir($mainRepo, 0777, true); + chdir($mainRepo); + exec('git init 2>/dev/null'); + + file_put_contents( + $this->repoDir . '/.git', + 'gitdir: ' . $mainRepo . '/.git' . "\n" + ); + + chdir($this->repoDir); + + $tester = $this->createCommandTester(); + + $exitCode = $tester->execute([]); + + self::assertSame(CsFixerGitHookCommand::FAILURE, $exitCode); + self::assertStringContainsString('.git directory not found', $tester->getDisplay()); + } + + public function testFailsWhenHooksDirCannotBeCreated(): void + { + chdir($this->repoDir); + exec('git init 2>/dev/null'); + exec('rm -rf ' . escapeshellarg($this->repoDir . '/.git/hooks')); + chmod($this->repoDir . '/.git', 0555); + + $tester = $this->createCommandTester(); + + $exitCode = $tester->execute([]); + + chmod($this->repoDir . '/.git', 0755); + + self::assertSame(CsFixerGitHookCommand::FAILURE, $exitCode); + self::assertStringContainsString('Could not create hooks directory', $tester->getDisplay()); + } + + public function testFailsWhenHookCopyFails(): void + { + chdir($this->repoDir); + exec('git init 2>/dev/null'); + chmod($this->repoDir . '/.git/hooks', 0555); + + $tester = $this->createCommandTester(); + + $exitCode = $tester->execute([]); + + chmod($this->repoDir . '/.git/hooks', 0755); + + self::assertSame(CsFixerGitHookCommand::FAILURE, $exitCode); + self::assertStringContainsString('Failed to copy hook', $tester->getDisplay()); + } + + public function testFailsWhenExistingHookCannotBeRemoved(): void + { + chdir($this->repoDir); + exec('git init 2>/dev/null'); + + $hooksDir = $this->repoDir . '/.git/hooks'; + file_put_contents($hooksDir . '/pre-commit', '#!/bin/sh'); + chmod($hooksDir, 0555); + + $tester = $this->createCommandTester(); + + $exitCode = $tester->execute([]); + + chmod($hooksDir, 0755); + + self::assertSame(CsFixerGitHookCommand::FAILURE, $exitCode); + self::assertStringContainsString('Could not remove existing hook', $tester->getDisplay()); + } + + public function testFailsWhenVendorDirNotFound(): void + { + chdir($this->repoDir); + exec('git init 2>/dev/null'); + + $tester = $this->createCommandTester(); + + $exitCode = $tester->execute([]); + + self::assertSame(CsFixerGitHookCommand::FAILURE, $exitCode); + self::assertStringContainsString('Could not find vendor directory', $tester->getDisplay()); + } + protected function setUp(): void { parent::setUp(); @@ -95,8 +215,20 @@ protected function tearDown(): void { chdir($this->oldCwd); if (is_dir($this->baseDir)) { + exec('chmod -R 755 ' . escapeshellarg($this->baseDir) . ' 2>/dev/null'); exec('rm -rf ' . escapeshellarg($this->baseDir)); } parent::tearDown(); } + + private function createCommandTester(): CommandTester + { + $app = new Application(); + $command = new CsFixerGitHookCommand(); + method_exists($app, 'addCommand') + ? $app->addCommand($command) + : $app->add($command); + + return new CommandTester($app->find('kununu:cs-fixer-git-hook')); + } } diff --git a/tests/Unit/Kununu/Sniffs/Classes/EmptyLineAfterClassElementsSniffTest.php b/tests/Unit/Kununu/Sniffs/Classes/EmptyLineAfterClassElementsSniffTest.php index 13a9be3..8c40258 100644 --- a/tests/Unit/Kununu/Sniffs/Classes/EmptyLineAfterClassElementsSniffTest.php +++ b/tests/Unit/Kununu/Sniffs/Classes/EmptyLineAfterClassElementsSniffTest.php @@ -10,7 +10,7 @@ class EmptyLineAfterClassElementsSniffTest extends SniffTestCase { public function testEmptyLineAfterClassElementsSniffer(): void { - $this->assertSnifferFindsFixableErrors(new EmptyLineAfterClassElementsSniff(), 2, 2); + $this->assertSnifferFindsFixableErrors(new EmptyLineAfterClassElementsSniff(), 4, 4); } public function testEmptyLineAfterClassElementsFixer(): void diff --git a/tests/Unit/Kununu/Sniffs/Files/LineLengthSniffTest.php b/tests/Unit/Kununu/Sniffs/Files/LineLengthSniffTest.php index d91b069..a86e0ad 100644 --- a/tests/Unit/Kununu/Sniffs/Files/LineLengthSniffTest.php +++ b/tests/Unit/Kununu/Sniffs/Files/LineLengthSniffTest.php @@ -10,6 +10,14 @@ class LineLengthSniffTest extends SniffTestCase { public function testMethodSignatureArgumentsSniffer(): void { - $this->assertSnifferFindsErrors(new LineLengthSniff(), 3); + $this->assertSnifferFindsErrors(new LineLengthSniff(), 4); + } + + public function testIgnoreUseStatementsCoversUseBranch(): void + { + $sniff = new LineLengthSniff(); + $sniff->ignoreUseStatements = true; + + $this->assertSnifferFindsErrors($sniff, 4); } } diff --git a/tests/_data/EmptyLineAfterClassElements/after/Fixme.php b/tests/_data/EmptyLineAfterClassElements/after/Fixme.php index 24dffa6..b067223 100644 --- a/tests/_data/EmptyLineAfterClassElements/after/Fixme.php +++ b/tests/_data/EmptyLineAfterClassElements/after/Fixme.php @@ -28,3 +28,40 @@ public function someMethod(): void $this->propertyOne = 'value'; } } + +class ExtraBlankAfterConst +{ + public const string A = 'a'; + + +public function doSomething(): void + { + } +} + +class ExtraBlankAfterProp +{ + private readonly string $prop; + + +public function doSomething(): void + { + } +} + +class PropertyThenClose +{ + protected string $onlyProperty; +} + +class ConstantThenClose +{ + public const string ONLY = 'only'; +} + +class NoPropertiesOrConstants +{ + public function firstMethod(): void + { + } +} diff --git a/tests/_data/EmptyLineAfterClassElements/before/Fixme.php b/tests/_data/EmptyLineAfterClassElements/before/Fixme.php index e9818f2..2d6f387 100644 --- a/tests/_data/EmptyLineAfterClassElements/before/Fixme.php +++ b/tests/_data/EmptyLineAfterClassElements/before/Fixme.php @@ -26,3 +26,42 @@ public function someMethod(): void $this->propertyOne = 'value'; } } + +class ExtraBlankAfterConst +{ + public const string A = 'a'; + + + + public function doSomething(): void + { + } +} + +class ExtraBlankAfterProp +{ + private readonly string $prop; + + + + public function doSomething(): void + { + } +} + +class PropertyThenClose +{ + protected string $onlyProperty; +} + +class ConstantThenClose +{ + public const string ONLY = 'only'; +} + +class NoPropertiesOrConstants +{ + public function firstMethod(): void + { + } +} diff --git a/tests/_data/LineLength/before/Fixme.php b/tests/_data/LineLength/before/Fixme.php index f2ec99c..c73cd7f 100644 --- a/tests/_data/LineLength/before/Fixme.php +++ b/tests/_data/LineLength/before/Fixme.php @@ -3,6 +3,8 @@ namespace Kununu; +use Some\Very\Long\Namespace\Path\That\Definitely\Exceeds\The\Maximum\Line\Length\Of\One\Hundred\And\Twenty\Characters\In\Total; + class Fixme { public const VERY_BIG_STRING = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam nec nisl nec nunc tincidunt tincidunt. Nullam nec nisl nec nunc tincidunt tincidunt.'; diff --git a/tests/_data/MethodSignatureArguments/after/Fixme.php b/tests/_data/MethodSignatureArguments/after/Fixme.php index c8ae728..4b82374 100644 --- a/tests/_data/MethodSignatureArguments/after/Fixme.php +++ b/tests/_data/MethodSignatureArguments/after/Fixme.php @@ -15,4 +15,20 @@ public function signatureArgumentsShouldBeMultiLine( string $thirdArgument ): void { } + + public function aReallyLongMethodNameWithNoParametersThatIsDefinitelyOverOneHundredAndTwentyCharactersForCoverage(): void + { + } + + public function multilineWithFirstLineParamsAndLongEnough(string $firstParamWithLongName, + string $secondParamWithAlsoALongName + ): void { + } + + public function multiThree( + int $a, + int $b, + int $c + ): void { + } } diff --git a/tests/_data/MethodSignatureArguments/before/Fixme.php b/tests/_data/MethodSignatureArguments/before/Fixme.php index 0f58659..55bc662 100644 --- a/tests/_data/MethodSignatureArguments/before/Fixme.php +++ b/tests/_data/MethodSignatureArguments/before/Fixme.php @@ -13,4 +13,20 @@ public function signatureArgumentsShouldBeSingleLine( public function signatureArgumentsShouldBeMultiLine(string $firstArgument, string $secondArgument, string $thirdArgument): void { } + + public function aReallyLongMethodNameWithNoParametersThatIsDefinitelyOverOneHundredAndTwentyCharactersForCoverage(): void + { + } + + public function multilineWithFirstLineParamsAndLongEnough(string $firstParamWithLongName, + string $secondParamWithAlsoALongName + ): void { + } + + public function multiThree( + int $a, + int $b, + int $c + ): void { + } }