From 76cab34b6700fa6f1fdb745fb8f95b123391d8eb Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Fri, 4 Jul 2025 15:42:16 +0200 Subject: [PATCH 1/3] PHPStan level 9 --- composer.json | 10 ++++--- phpstan.neon.dist | 13 +++++++++ src/Dist_Archive_Command.php | 51 ++++++++++++++++++++++-------------- 3 files changed, 52 insertions(+), 22 deletions(-) create mode 100644 phpstan.neon.dist diff --git a/composer.json b/composer.json index 1b0cff1..b72a38b 100644 --- a/composer.json +++ b/composer.json @@ -17,7 +17,7 @@ "inmarelibero/gitignore-checker": "^1.0.4" }, "require-dev": { - "wp-cli/wp-cli-tests": "^4", + "wp-cli/wp-cli-tests": "^5", "wp-cli/scaffold-command": "^2", "wp-cli/extension-command": "^2" }, @@ -46,12 +46,14 @@ "behat-rerun": "rerun-behat-tests", "lint": "run-linter-tests", "phpcs": "run-phpcs-tests", + "phpstan": "run-phpstan-tests", "phpcbf": "run-phpcbf-cleanup", "phpunit": "run-php-unit-tests", "prepare-tests": "install-package-tests", "test": [ "@lint", "@phpcs", + "@phpstan", "@phpunit", "@behat" ] @@ -59,7 +61,9 @@ "config": { "allow-plugins": { "dealerdirect/phpcodesniffer-composer-installer": true, - "johnpbloch/wordpress-core-installer": true - } + "johnpbloch/wordpress-core-installer": true, + "phpstan/extension-installer": true + }, + "lock": false } } diff --git a/phpstan.neon.dist b/phpstan.neon.dist new file mode 100644 index 0000000..824de93 --- /dev/null +++ b/phpstan.neon.dist @@ -0,0 +1,13 @@ +parameters: + level: 9 + paths: + - src + - dist-archive-command.php + scanDirectories: + - vendor/wp-cli/wp-cli/php + scanFiles: + - vendor/php-stubs/wordpress-stubs/wordpress-stubs.php + treatPhpDocTypesAsCertain: false + ignoreErrors: + - identifier: missingType.parameter + - identifier: missingType.return diff --git a/src/Dist_Archive_Command.php b/src/Dist_Archive_Command.php index 5c8e903..faf2df7 100644 --- a/src/Dist_Archive_Command.php +++ b/src/Dist_Archive_Command.php @@ -77,7 +77,7 @@ public function __invoke( $args, $assoc_args ) { $this->checker = new GitIgnoreChecker( $source_dir_path, '.distignore' ); $dist_ignore_filepath = $source_dir_path . '/.distignore'; if ( file_exists( $dist_ignore_filepath ) ) { - $file_ignore_rules = explode( PHP_EOL, file_get_contents( $dist_ignore_filepath ) ); + $file_ignore_rules = explode( PHP_EOL, (string) file_get_contents( $dist_ignore_filepath ) ); } else { WP_CLI::warning( 'No .distignore file found. All files in directory included in archive.' ); $file_ignore_rules = []; @@ -124,6 +124,8 @@ public function __invoke( $args, $assoc_args ) { chdir( dirname( $source_path ) ); + $cmd = "zip -r '{$archive_absolute_filepath}' {$archive_output_dir_name}"; + // If the files are being zipped in place, we need the exclusion rules. // whereas if they were copied for any reasons above, the rules have already been applied. if ( $source_path !== $source_dir_path || empty( $file_ignore_rules ) ) { @@ -175,10 +177,14 @@ function ( $ignored_file ) use ( $source_path ) { $escape_whitelist = 'targz' === $assoc_args['format'] ? array( '^', '*' ) : array(); WP_CLI::debug( "Running: {$cmd}", 'dist-archive' ); $escaped_shell_command = $this->escapeshellcmd( $cmd, $escape_whitelist ); - $ret = WP_CLI::launch( $escaped_shell_command, false, true ); + + /** + * @var WP_CLI\ProcessRun $ret + */ + $ret = WP_CLI::launch( $escaped_shell_command, false, true ); if ( 0 === $ret->return_code ) { $filename = pathinfo( $archive_absolute_filepath, PATHINFO_BASENAME ); - $file_size = $this->get_size_format( filesize( $archive_absolute_filepath ), 2 ); + $file_size = $this->get_size_format( (int) filesize( $archive_absolute_filepath ), 2 ); WP_CLI::success( "Created {$filename} (Size: {$file_size})" ); } else { @@ -201,7 +207,7 @@ function ( $ignored_file ) use ( $source_path ) { private function get_file_paths_and_names( $args, $assoc_args ) { $source_dir_path = realpath( $args[0] ); - if ( ! is_dir( $source_dir_path ) ) { + if ( ! $source_dir_path || ! is_dir( $source_dir_path ) ) { WP_CLI::error( 'Provided input path is not a directory.' ); } @@ -239,7 +245,7 @@ private function get_file_paths_and_names( $args, $assoc_args ) { $destination_dir_path = realpath( $destination_dir_path ); - if ( ! is_dir( $destination_dir_path ) ) { + if ( ! $destination_dir_path || ! is_dir( $destination_dir_path ) ) { WP_CLI::error( "Target directory does not exist: {$destination_dir_path}" ); } @@ -285,33 +291,42 @@ private function get_version( $source_dir_path ) { * @link https://developer.wordpress.org/reference/functions/get_file_data/ */ if ( file_exists( $source_dir_path . '/style.css' ) ) { - $contents = file_get_contents( $source_dir_path . '/style.css', false, null, 0, 5000 ); + $contents = (string) file_get_contents( $source_dir_path . '/style.css', false, null, 0, 5000 ); $contents = str_replace( "\r", "\n", $contents ); $pattern = '/^' . preg_quote( 'Version', ',' ) . ':(.*)$/mi'; if ( preg_match( $pattern, $contents, $match ) && $match[1] ) { - $version = trim( preg_replace( '/\s*(?:\*\/|\?>).*/', '', $match[1] ) ); + $version = trim( (string) preg_replace( '/\s*(?:\*\/|\?>).*/', '', $match[1] ) ); } } if ( empty( $version ) ) { - foreach ( glob( $source_dir_path . '/*.php' ) as $php_file ) { - $contents = file_get_contents( $php_file, false, null, 0, 5000 ); - $version = $this->get_version_in_code( $contents ); - if ( ! empty( $version ) ) { - $version = trim( $version ); + foreach ( (array) glob( $source_dir_path . '/*.php' ) as $php_file ) { + if ( ! $php_file ) { + continue; + } + $contents = (string) file_get_contents( $php_file, false, null, 0, 5000 ); + $ver = $this->get_version_in_code( $contents ); + if ( ! empty( $ver ) ) { + $version = trim( $ver ); break; } } } if ( empty( $version ) && file_exists( $source_dir_path . '/composer.json' ) ) { - $composer_obj = json_decode( file_get_contents( $source_dir_path . '/composer.json' ) ); - if ( ! empty( $composer_obj->version ) ) { + /** + * @var null|object{version?: string} $composer_obj + */ + $composer_obj = json_decode( (string) file_get_contents( $source_dir_path . '/composer.json' ) ); + if ( $composer_obj && ! empty( $composer_obj->version ) ) { $version = trim( $composer_obj->version ); } } if ( ! empty( $version ) && false !== stripos( $version, '-alpha' ) && is_dir( $source_dir_path . '/.git' ) ) { + /** + * @var WP_CLI\ProcessRun $response + */ $response = WP_CLI::launch( "cd {$source_dir_path}; git log --pretty=format:'%h' -n 1", false, true ); $maybe_hash = trim( $response->stdout ); if ( $maybe_hash && 7 === strlen( $maybe_hash ) ) { @@ -387,7 +402,7 @@ private function get_version_in_docblock( $docblock ) { * @see https://github.com/phpactor/docblock/blob/master/lib/Parser.php * * @param string $docblock Docblock to parse. - * @return array Associative array of parsed data. + * @return array Associative array of parsed data. */ private function parse_doc_block( $docblock ) { $tag_documentor = '{@([a-zA-Z0-9-_\\\]+)\s*?(.*)?}'; @@ -402,7 +417,7 @@ private function parse_doc_block( $docblock ) { } } - $tag_name = strtolower( $matches[1] ); + $tag_name = trim( isset( $matches[1] ) ? $matches[1] : '' ); $metadata = trim( isset( $matches[2] ) ? $matches[2] : '' ); $tags[ $tag_name ] = $metadata; @@ -456,7 +471,6 @@ protected function is_path_contains_symlink( $source_dir_path ) { ); /** - * @var RecursiveIteratorIterator $iterator * @var SplFileInfo $item */ foreach ( $iterator as $item ) { @@ -487,7 +501,6 @@ private function get_file_list( $source_dir_path, $excluded = false ) { ); /** - * @var RecursiveIteratorIterator $iterator * @var SplFileInfo $item */ foreach ( $iterator as $item ) { @@ -499,7 +512,7 @@ private function get_file_list( $source_dir_path, $excluded = false ) { $included_files[] = $relative_filepath; } } catch ( \Inmarelibero\GitIgnoreChecker\Exception\InvalidArgumentException $exception ) { - if ( $item->isLink() && ! file_exists( readlink( $item->getPathname() ) ) ) { + if ( $item->isLink() && ! file_exists( (string) readlink( $item->getPathname() ) ) ) { WP_CLI::error( "Broken symlink at {$relative_filepath}. Target missing at {$item->getLinkTarget()}." ); } else { WP_CLI::error( $exception->getMessage() ); From 8704c6ae45beba7dfe9c6965543241299990009d Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Fri, 4 Jul 2025 15:42:36 +0200 Subject: [PATCH 2/3] Bump testVersion --- phpcs.xml.dist | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpcs.xml.dist b/phpcs.xml.dist index 88e4f5d..72fe46e 100644 --- a/phpcs.xml.dist +++ b/phpcs.xml.dist @@ -38,7 +38,7 @@ - + From 5fc8f38f532f30cafd38098db11bedfdf1284a72 Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Fri, 4 Jul 2025 15:56:29 +0200 Subject: [PATCH 3/3] Add back strtolower --- src/Dist_Archive_Command.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Dist_Archive_Command.php b/src/Dist_Archive_Command.php index faf2df7..01ca0bb 100644 --- a/src/Dist_Archive_Command.php +++ b/src/Dist_Archive_Command.php @@ -305,7 +305,7 @@ private function get_version( $source_dir_path ) { continue; } $contents = (string) file_get_contents( $php_file, false, null, 0, 5000 ); - $ver = $this->get_version_in_code( $contents ); + $ver = $this->get_version_in_code( $contents ); if ( ! empty( $ver ) ) { $version = trim( $ver ); break; @@ -417,7 +417,7 @@ private function parse_doc_block( $docblock ) { } } - $tag_name = trim( isset( $matches[1] ) ? $matches[1] : '' ); + $tag_name = trim( isset( $matches[1] ) ? strtolower( $matches[1] ) : '' ); $metadata = trim( isset( $matches[2] ) ? $matches[2] : '' ); $tags[ $tag_name ] = $metadata;