diff --git a/resources/phpcs.xml b/resources/phpcs.xml new file mode 100644 index 00000000..5179500b --- /dev/null +++ b/resources/phpcs.xml @@ -0,0 +1,16 @@ + + + + + + + src/ + + */src/Generated/* + */src/Orm/*/Base/ + */src/Orm/*/Map/ + */src/Orm/Propel/ + + + + diff --git a/src/Builder/FileNormalizer/CodeSnifferCompositeNormalizer.php b/src/Builder/FileNormalizer/CodeSnifferCompositeNormalizer.php index 22618871..f45f9b86 100644 --- a/src/Builder/FileNormalizer/CodeSnifferCompositeNormalizer.php +++ b/src/Builder/FileNormalizer/CodeSnifferCompositeNormalizer.php @@ -9,6 +9,10 @@ namespace SprykerSdk\Integrator\Builder\FileNormalizer; +use SprykerSdk\Integrator\Composer\ComposerLockReaderInterface; +use SprykerSdk\Integrator\IntegratorConfig; +use Symfony\Component\Filesystem\Filesystem; + class CodeSnifferCompositeNormalizer implements FileNormalizerInterface { /** @@ -16,17 +20,62 @@ class CodeSnifferCompositeNormalizer implements FileNormalizerInterface */ public const ERROR_MESSAGE = 'Unable to execute code style fixer. Please manually execute it to adjust project code styles.'; + /** + * @var string + */ + public const SPRYKER_CS_PACKAGE = 'spryker/code-sniffer'; + + /** + * @var string + */ + public const PHP_CS_FIXER_PATH = 'vendor/bin/phpcbf'; + + /** + * @var string + */ + public const PHP_CS_FIXER_CONFIG_PATH = 'phpcs.xml'; + + /** + * @var string + */ + protected const INTERNAL_PHP_CS_FIXER_CONFIG_PATH = 'resources/phpcs.xml'; + /** * @var array<\SprykerSdk\Integrator\Builder\FileNormalizer\FileNormalizerInterface> */ protected array $codeSniffNormalizers; /** - * @param array<\SprykerSdk\Integrator\Builder\FileNormalizer\FileNormalizerInterface> $codeSniffNormalizers + * @var \SprykerSdk\Integrator\Composer\ComposerLockReaderInterface */ - public function __construct(array $codeSniffNormalizers) - { + protected ComposerLockReaderInterface $composerLockReader; + + /** + * @var \SprykerSdk\Integrator\IntegratorConfig + */ + protected IntegratorConfig $config; + + /** + * @var \Symfony\Component\Filesystem\Filesystem + */ + protected Filesystem $filesystem; + + /** + * @param array $codeSniffNormalizers + * @param \SprykerSdk\Integrator\Composer\ComposerLockReaderInterface $composerLockReader + * @param \SprykerSdk\Integrator\IntegratorConfig $config + * @param \Symfony\Component\Filesystem\Filesystem $filesystem + */ + public function __construct( + array $codeSniffNormalizers, + ComposerLockReaderInterface $composerLockReader, + IntegratorConfig $config, + Filesystem $filesystem + ) { $this->codeSniffNormalizers = $codeSniffNormalizers; + $this->composerLockReader = $composerLockReader; + $this->config = $config; + $this->filesystem = $filesystem; } /** @@ -34,7 +83,7 @@ public function __construct(array $codeSniffNormalizers) */ public function isApplicable(): bool { - return count(array_filter($this->codeSniffNormalizers, static fn (FileNormalizerInterface $normalizer): bool => $normalizer->isApplicable())) > 0; + return true; } /** @@ -51,6 +100,28 @@ public function getErrorMessage(): ?string * @return void */ public function normalize(array $filePaths): void + { + if (!$this->isPhpCsConfigMissed()) { + $this->executeNormalizers($filePaths); + + return; + } + + $this->filesystem->copy(static::getInitialCsFixerConfig(), $this->getProjectCSFixerConfigPath()); + + try { + $this->executeNormalizers($filePaths); + } finally { + $this->filesystem->remove($this->getProjectCSFixerConfigPath()); + } + } + + /** + * @param array $filePaths + * + * @return void + */ + protected function executeNormalizers(array $filePaths): void { foreach ($this->codeSniffNormalizers as $codeSniffNormalizer) { if (!$codeSniffNormalizer->isApplicable()) { @@ -60,4 +131,38 @@ public function normalize(array $filePaths): void $codeSniffNormalizer->normalize($filePaths); } } + + /** + * @return bool + */ + protected function isPhpCsConfigMissed(): bool + { + return $this->composerLockReader->getPackageData(static::SPRYKER_CS_PACKAGE) !== null + && $this->filesystem->exists($this->getProjectCSFixerPath()) + && !$this->filesystem->exists($this->getProjectCSFixerConfigPath()); + } + + /** + * @return string + */ + protected function getProjectCSFixerPath(): string + { + return $this->config->getProjectRootDirectory() . static::PHP_CS_FIXER_PATH; + } + + /** + * @return string + */ + protected function getProjectCSFixerConfigPath(): string + { + return $this->config->getProjectRootDirectory() . static::PHP_CS_FIXER_CONFIG_PATH; + } + + /** + * @return string + */ + public static function getInitialCsFixerConfig(): string + { + return dirname(__DIR__, 3) . DIRECTORY_SEPARATOR . static::INTERNAL_PHP_CS_FIXER_CONFIG_PATH; + } } diff --git a/src/Composer/ComposerLockReader.php b/src/Composer/ComposerLockReader.php index aef8837b..ae13abf4 100644 --- a/src/Composer/ComposerLockReader.php +++ b/src/Composer/ComposerLockReader.php @@ -78,6 +78,30 @@ public function getModuleVersions(): array return $packages; } + /** + * @param string $packageName + * + * @return array|null + */ + public function getPackageData(string $packageName): ?array + { + $composerLockData = $this->getProjectComposerLockData(); + + foreach (static::GROUP_PACKAGES as $packagesKey) { + if (!isset($composerLockData[$packagesKey])) { + continue; + } + + foreach ($composerLockData[$packagesKey] as $packageData) { + if ($packageData['name'] === $packageName) { + return $packageData; + } + } + } + + return null; + } + /** * @param array $packageData * diff --git a/src/Composer/ComposerLockReaderInterface.php b/src/Composer/ComposerLockReaderInterface.php index 675ad1a1..88dcaf2a 100644 --- a/src/Composer/ComposerLockReaderInterface.php +++ b/src/Composer/ComposerLockReaderInterface.php @@ -15,4 +15,11 @@ interface ComposerLockReaderInterface * @return array */ public function getModuleVersions(): array; + + /** + * @param string $packageName + * + * @return array|null + */ + public function getPackageData(string $packageName): ?array; } diff --git a/src/IntegratorFactory.php b/src/IntegratorFactory.php index 5c0a556b..ee79e34e 100644 --- a/src/IntegratorFactory.php +++ b/src/IntegratorFactory.php @@ -509,7 +509,7 @@ public function createWireTransferManifestStrategy(): ManifestStrategyInterface { return new WireTransferManifestStrategy( $this->getConfig(), - new Filesystem(), + $this->createFilesystem(), ); } @@ -520,7 +520,7 @@ public function createWireSchemaManifestStrategy(): ManifestStrategyInterface { return new WireSchemaManifestStrategy( $this->getConfig(), - new Filesystem(), + $this->createFilesystem(), ); } @@ -563,6 +563,9 @@ public function createCodeSnifferCompositeNormalizer(): FileNormalizerInterface $this->createPhpCSFixerNormalizer(), $this->createCodeSniffStyleFileNormalizer(), ], + $this->createComposerLockReader(), + $this->getConfig(), + $this->createFilesystem(), ); } @@ -1165,6 +1168,14 @@ protected function createManifestToModulesRatingRequestMapper(): ManifestToModul return new ManifestToModulesRatingRequestMapper(); } + /** + * @return \Symfony\Component\Filesystem\Filesystem + */ + protected function createFilesystem(): Filesystem + { + return new Filesystem(); + } + /** * @return \SprykerSdk\Integrator\Builder\Finder\ClassConstantFinderInterface */ diff --git a/tests/SprykerSdkTest/Integrator/Builder/FileNormalizer/CodeSnifferCompositeNormalizerTest.php b/tests/SprykerSdkTest/Integrator/Builder/FileNormalizer/CodeSnifferCompositeNormalizerTest.php index 17f17a51..edd6b039 100644 --- a/tests/SprykerSdkTest/Integrator/Builder/FileNormalizer/CodeSnifferCompositeNormalizerTest.php +++ b/tests/SprykerSdkTest/Integrator/Builder/FileNormalizer/CodeSnifferCompositeNormalizerTest.php @@ -9,22 +9,31 @@ namespace SprykerSdkTest\Integrator\Builder\FileNormalizer; +use InvalidArgumentException; use PHPUnit\Framework\TestCase; use SprykerSdk\Integrator\Builder\FileNormalizer\CodeSnifferCompositeNormalizer; use SprykerSdk\Integrator\Builder\FileNormalizer\FileNormalizerInterface; +use SprykerSdk\Integrator\Composer\ComposerLockReaderInterface; +use SprykerSdk\Integrator\IntegratorConfig; +use Symfony\Component\Filesystem\Filesystem; class CodeSnifferCompositeNormalizerTest extends TestCase { /** * @return void */ - public function testIsApplicableShouldReturnTrueWhenOneOfNormalizerIsApplicable(): void + public function testIsApplicableShouldReturnAlwaysTrue(): void { // Arrange $normalizerOne = $this->createNormalizerMock(false, false); $normalizerTwo = $this->createNormalizerMock(true, false); - $codeSnifferCompositeNormalizer = new CodeSnifferCompositeNormalizer([$normalizerOne, $normalizerTwo]); + $codeSnifferCompositeNormalizer = new CodeSnifferCompositeNormalizer( + [$normalizerOne, $normalizerTwo], + $this->createComposerLockReaderMock(), + $this->createIntegratorConfigMock(), + $this->createFilesystemMock(), + ); // Act $result = $codeSnifferCompositeNormalizer->isApplicable(); @@ -36,31 +45,94 @@ public function testIsApplicableShouldReturnTrueWhenOneOfNormalizerIsApplicable( /** * @return void */ - public function testIsApplicableShouldReturnFalseWhenAllNormalizerNotApplicable(): void + public function testNormalizeShouldCallApplicableNormalizer(): void { - // Arrange + // Arrange & Assert $normalizerOne = $this->createNormalizerMock(false, false); - $normalizerTwo = $this->createNormalizerMock(false, false); + $normalizerTwo = $this->createNormalizerMock(true, true); - $codeSnifferCompositeNormalizer = new CodeSnifferCompositeNormalizer([$normalizerOne, $normalizerTwo]); + $codeSnifferCompositeNormalizer = new CodeSnifferCompositeNormalizer( + [$normalizerOne, $normalizerTwo], + $this->createComposerLockReaderMock(), + $this->createIntegratorConfigMock(), + $this->createFilesystemMock(), + ); // Act - $result = $codeSnifferCompositeNormalizer->isApplicable(); + $codeSnifferCompositeNormalizer->normalize([]); + } + + /** + * @return void + */ + public function testGetErrorMessageShouldReturnErrorMessage(): void + { + // Arrange + $codeSnifferCompositeNormalizer = new CodeSnifferCompositeNormalizer( + [$this->createMock(FileNormalizerInterface::class)], + $this->createComposerLockReaderMock(), + $this->createIntegratorConfigMock(), + $this->createFilesystemMock(), + ); + + // Act + $errorMessage = $codeSnifferCompositeNormalizer->getErrorMessage(); // Assert - $this->assertFalse($result); + $this->assertSame(CodeSnifferCompositeNormalizer::ERROR_MESSAGE, $errorMessage); + } + + /** + * @dataProvider missedPhpConfigConditionsDataProvider + * + * @param bool $isSprykerPackageInstalled + * @param bool $isCsFixerExecutableFound + * @param bool $isCsFixerConfigFound + * + * @return void + */ + public function testNormalizeShouldSkipConfigCoppingWhenConditionFalse( + bool $isSprykerPackageInstalled, + bool $isCsFixerExecutableFound, + bool $isCsFixerConfigFound + ): void { + // Arrange & Assert + $codeSnifferCompositeNormalizer = new CodeSnifferCompositeNormalizer( + [$this->createNormalizerMock(true, true)], + $this->createComposerLockReaderMock($isSprykerPackageInstalled), + $this->createIntegratorConfigMock(), + $this->createFilesystemMock($isCsFixerExecutableFound, $isCsFixerConfigFound), + ); + + // Act + $codeSnifferCompositeNormalizer->normalize([]); + } + + /** + * @return array> + */ + public function missedPhpConfigConditionsDataProvider(): array + { + return [ + [false, true, false], + [true, false, false], + [true, true, true], + ]; } /** * @return void */ - public function testIsApplicableShouldNormalizeApplicableNormalizer(): void + public function testNormalizeShouldAddAndRemoveMissedPhpcsConfigAndCallApplicableNormalizer(): void { // Arrange & Assert - $normalizerOne = $this->createNormalizerMock(false, false); - $normalizerTwo = $this->createNormalizerMock(true, true); - $codeSnifferCompositeNormalizer = new CodeSnifferCompositeNormalizer([$normalizerOne, $normalizerTwo]); + $codeSnifferCompositeNormalizer = new CodeSnifferCompositeNormalizer( + [$this->createNormalizerMock(true, true)], + $this->createComposerLockReaderMock(true), + $this->createIntegratorConfigMock(), + $this->createFilesystemMock(true, false, true), + ); // Act $codeSnifferCompositeNormalizer->normalize([]); @@ -69,16 +141,24 @@ public function testIsApplicableShouldNormalizeApplicableNormalizer(): void /** * @return void */ - public function testGetErrorMessageShouldReturnErrorMessage(): void + public function testNormalizeShouldRemoveMissedPhpcsConfigWhenExceptionThrown(): void { - // Arrange - $codeSnifferCompositeNormalizer = new CodeSnifferCompositeNormalizer([$this->createMock(FileNormalizerInterface::class)]); + // Arrange & Assert + $this->expectException(InvalidArgumentException::class); - // Act - $errorMessage = $codeSnifferCompositeNormalizer->getErrorMessage(); + $fileNormalizersMock = $this->createMock(FileNormalizerInterface::class); + $fileNormalizersMock->method('isApplicable')->willReturn(true); + $fileNormalizersMock->method('normalize')->willThrowException(new InvalidArgumentException('')); - // Assert - $this->assertSame(CodeSnifferCompositeNormalizer::ERROR_MESSAGE, $errorMessage); + $codeSnifferCompositeNormalizer = new CodeSnifferCompositeNormalizer( + [$fileNormalizersMock], + $this->createComposerLockReaderMock(true), + $this->createIntegratorConfigMock(), + $this->createFilesystemMock(true, false, true), + ); + + // Act + $codeSnifferCompositeNormalizer->normalize([]); } /** @@ -99,4 +179,63 @@ protected function createNormalizerMock(bool $isApplicable, bool $shouldCall, ?s return $fileNormalizersMock; } + + /** + * @param bool $isSprykerPackageInstalled + * + * @return \SprykerSdk\Integrator\Composer\ComposerLockReaderInterface + */ + protected function createComposerLockReaderMock(bool $isSprykerPackageInstalled = false): ComposerLockReaderInterface + { + $composerLockReader = $this->createMock(ComposerLockReaderInterface::class); + $composerLockReader->method('getPackageData')->willReturn( + $isSprykerPackageInstalled ? ['name' => CodeSnifferCompositeNormalizer::SPRYKER_CS_PACKAGE] : null, + ); + + return $composerLockReader; + } + + /** + * @param bool $isCsFixerExecutableFound + * @param bool $isCsFixerConfigFound + * @param bool $shouldInvokeConfigCopping + * + * @return \Symfony\Component\Filesystem\Filesystem + */ + protected function createFilesystemMock( + bool $isCsFixerExecutableFound = false, + bool $isCsFixerConfigFound = true, + bool $shouldInvokeConfigCopping = false + ): Filesystem { + $filesystem = $this->createMock(Filesystem::class); + + $filesystem->method('exists') + ->willReturnMap([ + [CodeSnifferCompositeNormalizer::PHP_CS_FIXER_PATH, $isCsFixerExecutableFound], + [CodeSnifferCompositeNormalizer::PHP_CS_FIXER_CONFIG_PATH, $isCsFixerConfigFound], + ]); + + $filesystem + ->expects($shouldInvokeConfigCopping ? $this->once() : $this->never()) + ->method('copy') + ->with(CodeSnifferCompositeNormalizer::getInitialCsFixerConfig()); + + $filesystem + ->expects($shouldInvokeConfigCopping ? $this->once() : $this->never()) + ->method('remove') + ->with(CodeSnifferCompositeNormalizer::PHP_CS_FIXER_CONFIG_PATH); + + return $filesystem; + } + + /** + * @return \SprykerSdk\Integrator\IntegratorConfig + */ + protected function createIntegratorConfigMock(): IntegratorConfig + { + $integratorConfig = $this->createMock(IntegratorConfig::class); + $integratorConfig->method('getProjectRootDirectory')->willReturn(''); + + return $integratorConfig; + } }