diff --git a/CustomizeCommand.php b/CustomizeCommand.php index 928e129..c700c15 100644 --- a/CustomizeCommand.php +++ b/CustomizeCommand.php @@ -26,9 +26,6 @@ * * It supports passing answers as a JSON string via the `--answers` option * or the `CUSTOMIZER_ANSWERS` environment variable. - * - * @SuppressWarnings(PHPMD.ExcessiveClassComplexity) - * @SuppressWarnings(PHPMD.TooManyPublicMethods) */ class CustomizeCommand extends BaseCommand { @@ -60,7 +57,7 @@ class CustomizeCommand extends BaseCommand { /** * Composer config data loaded before customization. * - * @var array + * @var array */ public array $composerjsonData; @@ -141,9 +138,6 @@ protected function execute(InputInterface $input, OutputInterface $output): int * The answers to the questions as an associative array: * - key: The question key. * - value: The answer to the question. - * - * @SuppressWarnings(PHPMD.CyclomaticComplexity) - * @SuppressWarnings(PHPMD.NPathComplexity) */ protected function askQuestions(): array { $answers = []; @@ -216,8 +210,6 @@ protected function processAnswers(array $answers): void { /** * Cleanup after customization. - * - * @SuppressWarnings(PHPMD.CyclomaticComplexity) */ protected function cleanupSelf(): void { if (!empty($this->configClass)) { @@ -332,26 +324,42 @@ protected function init(string $cwd, InputInterface $input, OutputInterface $out // // Convert the answers (if provided) to an input stream to be used for the // interactive prompts. - $answers = getenv('CUSTOMIZER_ANSWERS'); - $answers = $answers ?: $input->getOption('answers'); + $answers = getenv('CUSTOMIZER_ANSWERS') ?: $input->getOption('answers'); if ($answers && is_string($answers)) { $answers = json_decode($answers, TRUE); - if (is_array($answers)) { - $stream = fopen('php://memory', 'r+'); - if ($stream === FALSE) { + if (!is_array($answers)) { + // @codeCoverageIgnoreStart + throw new \InvalidArgumentException('Answers must be a JSON string'); + // @codeCoverageIgnoreEnd + } + + $answers = array_map(function ($answer): string { + if (!is_scalar($answer) && !is_null($answer)) { // @codeCoverageIgnoreStart - throw new \RuntimeException('Failed to open memory stream'); + throw new \InvalidArgumentException('Answer must be a scalar value'); // @codeCoverageIgnoreEnd } - foreach ($answers as $answer) { - fwrite($stream, $answer . \PHP_EOL); - } - rewind($stream); - $input = new ArgvInput($answers); - $input->setStream($stream); + return strval($answer); + }, $answers); + $answers = array_values($answers); + + $stream = fopen('php://memory', 'r+'); + if ($stream === FALSE) { + // @codeCoverageIgnoreStart + throw new \RuntimeException('Failed to open memory stream'); + // @codeCoverageIgnoreEnd + } + + foreach ($answers as $answer) { + fwrite($stream, $answer . \PHP_EOL); } + + rewind($stream); + + $input = new ArgvInput($answers); + $input->setStream($stream); } $this->io = new SymfonyStyle($input, $output); @@ -457,7 +465,7 @@ public static function finder(string $dir, ?array $exclude = NULL): Finder { * * This is a helper method to be used in processing callbacks. * - * @return array + * @return array * Composer.json data as an associative array. */ public static function readComposerJson(string $file): array { @@ -591,8 +599,6 @@ public static function uncommentLine(string $path, string $search, string $comme * Value to unset. If NULL, the whole key will be unset. * @param bool $exact * Match value exactly or by substring. - * - * @SuppressWarnings(PHPMD.CyclomaticComplexity) */ public static function arrayUnsetDeep(array &$array, array $path, ?string $value = NULL, bool $exact = TRUE): void { $key = array_shift($path); @@ -611,6 +617,8 @@ public static function arrayUnsetDeep(array &$array, array $path, ?string $value if ($value !== NULL) { if (is_array($array[$key])) { $array[$key] = array_filter($array[$key], static function ($item) use ($value, $exact): bool { + $item = is_scalar($item) ? strval($item) : ''; + return $exact ? $item !== $value : !str_contains($item, $value); }); if (count(array_filter(array_keys($array[$key]), 'is_int')) === count($array[$key])) { @@ -646,8 +654,6 @@ public static function arrayUnsetDeep(array &$array, array $path, ?string $value * @return array|string> * An associative array of messages with message name as key and the message * test as a string or an array of strings. - * - * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ public static function messages(CustomizeCommand $c): array { return [ @@ -692,8 +698,6 @@ public static function messages(CustomizeCommand $c): array { * be passed to the question callback. The callback receives the following * arguments: * - command: The CustomizeCommand object. - * - * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ public static function questions(CustomizeCommand $c): array { return []; @@ -710,8 +714,6 @@ public static function questions(CustomizeCommand $c): array { * Gathered answers. * @param CustomizeCommand $c * The CustomizeCommand object. - * - * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ public static function process(array $answers, CustomizeCommand $c): void { @@ -737,8 +739,6 @@ public static function process(array $answers, CustomizeCommand $c): void { * @return bool * Return FALSE to skip the further self-cleanup. Returning TRUE will * proceed with the self-cleanup. - * - * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ public static function cleanup(CustomizeCommand $c): bool { return TRUE; diff --git a/README.md b/README.md index eb95807..4a0eef6 100644 --- a/README.md +++ b/README.md @@ -168,8 +168,6 @@ use AlexSkrypnyk\Customizer\CustomizeCommand; * Example configuration for the Customizer command. * * phpcs:disable Drupal.Classes.ClassFileName.NoMatch - * - * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ class Customize { @@ -200,9 +198,6 @@ class Customize { * be passed to the question callback. The callback receives the following * arguments: * - command: The CustomizeCommand object. - * - * @SuppressWarnings(PHPMD.UnusedFormalParameter) - * @SuppressWarnings(PHPMD.ExcessiveMethodLength) */ public static function questions(CustomizeCommand $c): array { // This an example of questions that can be asked to customize the project. @@ -343,8 +338,6 @@ class Customize { * @return array> * An associative array of messages with message name as key and the message * test as a string or an array of strings. - * - * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ public static function messages(CustomizeCommand $c): array { return [ diff --git a/composer.json b/composer.json index e69fa2b..bc9e690 100644 --- a/composer.json +++ b/composer.json @@ -26,10 +26,9 @@ "drupal/coder": "^8.3", "ergebnis/composer-normalize": "^2.42", "phpcompatibility/php-compatibility": "^9.3", - "phpmd/phpmd": "^2.15", - "phpstan/phpstan": "^1.10", + "phpstan/phpstan": "^2", "phpunit/phpunit": "^11.1", - "rector/rector": "^1.0" + "rector/rector": "^2" }, "minimum-stability": "dev", "prefer-stable": true, @@ -62,7 +61,6 @@ ], "lint": [ "phpcs", - "phpmd --exclude vendor . text phpmd.xml", "phpstan", "rector --clear-cache --dry-run" ], diff --git a/customize.php b/customize.php index ab5c4c9..2449219 100644 --- a/customize.php +++ b/customize.php @@ -10,8 +10,6 @@ * Example configuration for the Customizer command. * * phpcs:disable Drupal.Classes.ClassFileName.NoMatch - * - * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ class Customize { @@ -42,9 +40,6 @@ class Customize { * be passed to the question callback. The callback receives the following * arguments: * - command: The CustomizeCommand object. - * - * @SuppressWarnings(PHPMD.UnusedFormalParameter) - * @SuppressWarnings(PHPMD.ExcessiveMethodLength) */ public static function questions(CustomizeCommand $c): array { // This an example of questions that can be asked to customize the project. @@ -185,8 +180,6 @@ public static function cleanup(CustomizeCommand $c): bool { * @return array> * An associative array of messages with message name as key and the message * test as a string or an array of strings. - * - * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ public static function messages(CustomizeCommand $c): array { return [ diff --git a/phpmd.xml b/phpmd.xml deleted file mode 100644 index 9697f28..0000000 --- a/phpmd.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - - - */tests/* - diff --git a/phpunit.xml b/phpunit.xml index fb8c74f..82db894 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -1,23 +1,23 @@ - - + displayDetailsOnTestsThatTriggerNotices="true" + displayDetailsOnTestsThatTriggerDeprecations="true" + displayDetailsOnPhpunitDeprecations="true"> + tests/phpunit - @@ -26,18 +26,15 @@ Plugin.php - tests/phpunit + tests - - + - + diff --git a/rector.php b/rector.php index 9e7772d..3c87941 100644 --- a/rector.php +++ b/rector.php @@ -6,8 +6,6 @@ * * Usage: * ./vendor/bin/rector process . - * - * @see https://github.com/palantirnet/drupal-rector/blob/main/rector.php */ declare(strict_types=1); @@ -19,30 +17,32 @@ use Rector\CodingStyle\Rector\Stmt\NewlineAfterStatementRector; use Rector\Config\RectorConfig; use Rector\DeadCode\Rector\If_\RemoveAlwaysTrueIfConditionRector; +use Rector\PHPUnit\Set\PHPUnitSetList; use Rector\Set\ValueObject\SetList; use Rector\Strict\Rector\Empty_\DisallowedEmptyRuleFixerRector; use Rector\TypeDeclaration\Rector\StmtsAwareInterface\DeclareStrictTypesRector; -return static function (RectorConfig $rectorConfig): void { - $rectorConfig->paths([ +return static function (RectorConfig $config): void { + $config->paths([ __DIR__ . '/CustomizeCommand.php', __DIR__ . '/Plugin.php', __DIR__ . '/customize.php', __DIR__ . '/tests/phpunit', ]); - $rectorConfig->sets([ + $config->sets([ SetList::PHP_82, SetList::CODE_QUALITY, SetList::CODING_STYLE, SetList::DEAD_CODE, SetList::INSTANCEOF, SetList::TYPE_DECLARATION, + PHPUnitSetList::PHPUNIT_100, ]); - $rectorConfig->rule(DeclareStrictTypesRector::class); + $config->rule(DeclareStrictTypesRector::class); - $rectorConfig->skip([ + $config->skip([ // Rules added by Rector's rule sets. CountArrayToEmptyArrayComparisonRector::class, DisallowedEmptyRuleFixerRector::class, @@ -56,11 +56,11 @@ '*/node_modules/*', ]); - $rectorConfig->fileExtensions([ + $config->fileExtensions([ 'php', 'inc', ]); - $rectorConfig->importNames(TRUE, FALSE); - $rectorConfig->importShortClasses(FALSE); + $config->importNames(TRUE, FALSE); + $config->importShortClasses(FALSE); }; diff --git a/tests/phpunit/Fixtures/install/base/customize.php b/tests/phpunit/Fixtures/install/base/customize.php index 22c1e69..59900b7 100644 --- a/tests/phpunit/Fixtures/install/base/customize.php +++ b/tests/phpunit/Fixtures/install/base/customize.php @@ -8,8 +8,6 @@ * Customizer configuration. * * phpcs:disable Drupal.Classes.ClassFileName.NoMatch - * - * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ class Customize { diff --git a/tests/phpunit/Fixtures/install_additional_cleanup/base/customize.php b/tests/phpunit/Fixtures/install_additional_cleanup/base/customize.php index f6501a1..1c148a5 100644 --- a/tests/phpunit/Fixtures/install_additional_cleanup/base/customize.php +++ b/tests/phpunit/Fixtures/install_additional_cleanup/base/customize.php @@ -8,8 +8,6 @@ * Customizer configuration. * * phpcs:disable Drupal.Classes.ClassFileName.NoMatch - * - * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ class Customize { diff --git a/tests/phpunit/Fixtures/no_install/1_before_install/customize.php b/tests/phpunit/Fixtures/no_install/1_before_install/customize.php index f6501a1..1c148a5 100644 --- a/tests/phpunit/Fixtures/no_install/1_before_install/customize.php +++ b/tests/phpunit/Fixtures/no_install/1_before_install/customize.php @@ -8,8 +8,6 @@ * Customizer configuration. * * phpcs:disable Drupal.Classes.ClassFileName.NoMatch - * - * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ class Customize { diff --git a/tests/phpunit/Fixtures/no_install/2_post_install/customize.php b/tests/phpunit/Fixtures/no_install/2_post_install/customize.php index f6501a1..1c148a5 100644 --- a/tests/phpunit/Fixtures/no_install/2_post_install/customize.php +++ b/tests/phpunit/Fixtures/no_install/2_post_install/customize.php @@ -8,8 +8,6 @@ * Customizer configuration. * * phpcs:disable Drupal.Classes.ClassFileName.NoMatch - * - * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ class Customize { diff --git a/tests/phpunit/Fixtures/no_install/base/customize.php b/tests/phpunit/Fixtures/no_install/base/customize.php index f6501a1..1c148a5 100644 --- a/tests/phpunit/Fixtures/no_install/base/customize.php +++ b/tests/phpunit/Fixtures/no_install/base/customize.php @@ -8,8 +8,6 @@ * Customizer configuration. * * phpcs:disable Drupal.Classes.ClassFileName.NoMatch - * - * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ class Customize { diff --git a/tests/phpunit/Functional/CustomizerTestCase.php b/tests/phpunit/Functional/CustomizerTestCase.php index 8fc1ac9..893fbdc 100644 --- a/tests/phpunit/Functional/CustomizerTestCase.php +++ b/tests/phpunit/Functional/CustomizerTestCase.php @@ -126,9 +126,7 @@ protected function initComposerTester(): void { $application = new Application(); $application->setAutoExit(FALSE); $application->setCatchExceptions(FALSE); - if (method_exists($application, 'setCatchErrors')) { - $application->setCatchErrors(FALSE); - } + $application->setCatchErrors(FALSE); $this->tester = new ApplicationTester($application); @@ -201,8 +199,7 @@ protected function initLocations(string $cwd, ?callable $cb = NULL): void { $this->fs->mirror(static::$fixtures . DIRECTORY_SEPARATOR . 'base', static::$repo); } - if ($cb !== NULL && is_callable($cb) && $cb instanceof \Closure) { - // @phpstan-ignore-next-line + if ($cb !== NULL && $cb instanceof \Closure) { \Closure::bind($cb, $this, self::class)(); } } @@ -337,12 +334,21 @@ protected function assertComposerJsonFilesEqual(string $expected, string $actual if (!is_array($data)) { $this->fail('The actual file is not a valid JSON file.'); } + unset($data['minimum-stability']); + + if (!is_array($data['repositories'])) { + $this->fail('The actual file does not contain the repositories section.'); + } foreach ($data['repositories'] as $key => $repository) { - if ($repository['type'] === 'path' && $repository['url'] === static::$root) { + if (!is_array($repository)) { + $this->fail('The actual file contains an invalid repository entry.'); + } + if (array_key_exists('type', $repository) && $repository['type'] === 'path' && array_key_exists('url', $repository) && $repository['url'] === static::$root) { unset($data['repositories'][$key]); } } + if (empty($data['repositories'])) { unset($data['repositories']); } @@ -375,8 +381,8 @@ protected function assertComposerCommandSuccessOutputContains(string|array $stri * Assert that fixtures directories are equal. */ protected function assertFixtureDirectoryEqualsSut(string $expected): void { - $this->assertDirectoriesEqual(static::$fixtures . DIRECTORY_SEPARATOR . $expected, static::$sut, static function (string $content, \SplFileInfo $file) : string { - // Remove compose.json overrides added by the static::setUp(). + $this->assertDirectoriesEqual(static::$fixtures . DIRECTORY_SEPARATOR . $expected, static::$sut, static function (string $content, \SplFileInfo $file): string { + // Remove compose.json overrides added by the static::setUp(). if ($file->getBasename() === 'composer.json') { $data = json_decode($content, TRUE); if (!is_array($data)) { @@ -384,8 +390,16 @@ protected function assertFixtureDirectoryEqualsSut(string $expected): void { } unset($data['minimum-stability']); if (isset($data['repositories'])) { + if (!is_array($data['repositories'])) { + return $content; + } + foreach ($data['repositories'] as $key => $repository) { - if ($repository['type'] === 'path' && $repository['url'] === static::$root) { + if (!is_array($repository)) { + return $content; + } + + if (array_key_exists('type', $repository) && $repository['type'] === 'path' && array_key_exists('url', $repository) && $repository['url'] === static::$root) { unset($data['repositories'][$key]); if (empty($data['repositories'])) { unset($data['repositories']); @@ -395,7 +409,8 @@ protected function assertFixtureDirectoryEqualsSut(string $expected): void { } $content = json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) . PHP_EOL; } - return $content; + + return $content; }); } @@ -621,6 +636,7 @@ protected function assertDirectoriesEqual(string $dir1, string $dir2, ?callable throw new AssertionFailedError($message); } + // @phpstan-ignore-next-line $this->assertTrue(TRUE); } diff --git a/tests/phpunit/Unit/FilesTest.php b/tests/phpunit/Unit/FilesTest.php index 204250d..3a0ccc4 100644 --- a/tests/phpunit/Unit/FilesTest.php +++ b/tests/phpunit/Unit/FilesTest.php @@ -43,8 +43,6 @@ public function testReplaceInPath(string $path, array $before, string $search, s * * @return array * The data. - * - * @SuppressWarnings(PHPMD.ExcessiveMethodLength) */ public static function dataProviderReplaceInPath(): array { return [