From ae0d47ce83cbc23740b84069782c6f8b84e1b4f7 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Mon, 27 Nov 2023 04:20:29 +0100 Subject: [PATCH] Composer: implement CS menu and extra commands This adds a number of extra CS-related Composer scripts and handler functions. This: * Uses the same menu as is already in use in the Free, Premium and several other plugins for predictability. * Where necessary, adjusts existing Composer scripts to, again, be in line with what is already in use in Free and Premium. This also adds the _ability_ to run CS with a Threshold. As the codebase is currently clean, this ability is not activated for CI for the time being, though it is likely that it will be activated once YoastCS 3.0 will be used. Note: this change does mean that, in contrast to before, running `check-cs` will now return only errors. While this is new for this package, it is in line with the expected behaviour for the script as used in other packages. --- .gitattributes | 1 + .github/workflows/cs.yml | 2 +- composer.json | 43 ++++++-- config/composer/actions.php | 189 ++++++++++++++++++++++++++++++++++++ 4 files changed, 225 insertions(+), 10 deletions(-) create mode 100644 config/composer/actions.php diff --git a/.gitattributes b/.gitattributes index 329ab71..e676aad 100644 --- a/.gitattributes +++ b/.gitattributes @@ -7,6 +7,7 @@ # /.cache export-ignore /.github export-ignore +/config export-ignore /grunt export-ignore /tests export-ignore .eslintrc export-ignore diff --git a/.github/workflows/cs.yml b/.github/workflows/cs.yml index 8fa40f4..09b4281 100644 --- a/.github/workflows/cs.yml +++ b/.github/workflows/cs.yml @@ -63,7 +63,7 @@ jobs: # @link https://github.com/staabm/annotate-pull-request-from-checkstyle/ - name: Check PHP code style id: phpcs - run: composer check-cs -- --no-cache --report-full --report-checkstyle=./phpcs-report.xml + run: composer check-cs-warnings -- --no-cache --report-full --report-checkstyle=./phpcs-report.xml - name: Show PHPCS results in PR if: ${{ always() && steps.phpcs.outcome == 'failure' }} diff --git a/composer.json b/composer.json index 11bc0f4..4ef5218 100644 --- a/composer.json +++ b/composer.json @@ -58,6 +58,9 @@ ] }, "autoload-dev": { + "classmap": [ + "config/" + ], "psr-4": { "Yoast\\WP\\ACF\\Tests\\": "tests/" }, @@ -78,28 +81,50 @@ "lint": [ "@php ./vendor/php-parallel-lint/php-parallel-lint/parallel-lint . -e php --show-deprecated --exclude vendor --exclude node_modules --exclude .git" ], - "test": [ - "@php ./vendor/phpunit/phpunit/phpunit --no-coverage" + "cs": [ + "Yoast\\WP\\ACF\\Config\\Composer\\Actions::check_coding_standards" ], - "coverage": [ - "@php ./vendor/phpunit/phpunit/phpunit" + "check-cs-thresholds": [ + "@putenv YOASTCS_THRESHOLD_ERRORS=0", + "@putenv YOASTCS_THRESHOLD_WARNINGS=0", + "Yoast\\WP\\ACF\\Config\\Composer\\Actions::check_cs_thresholds" ], "check-cs": [ - "@php ./vendor/squizlabs/php_codesniffer/bin/phpcs" + "@check-cs-warnings -n" ], "check-cs-errors": [ "@check-cs" ], + "check-cs-warnings": [ + "@php ./vendor/squizlabs/php_codesniffer/bin/phpcs" + ], + "check-staged-cs": [ + "@check-cs-warnings --filter=GitStaged" + ], + "check-branch-cs": [ + "Yoast\\WP\\ACF\\Config\\Composer\\Actions::check_branch_cs" + ], "fix-cs": [ "@php ./vendor/squizlabs/php_codesniffer/bin/phpcbf" + ], + "test": [ + "@php ./vendor/phpunit/phpunit/phpunit --no-coverage" + ], + "coverage": [ + "@php ./vendor/phpunit/phpunit/phpunit" ] }, "scripts-descriptions": { "lint": "Check the PHP files for parse errors.", - "test": "Run the unit tests without code coverage.", - "coverage": "Run the unit tests with code coverage.", - "check-cs": "Check the PHP files for code style violations and best practices.", + "cs": "See a menu with the code style checking script options.", + "check-cs-thresholds": "Check the PHP files for code style violations and best practices and verify the number of issues does not exceed predefined thresholds.", + "check-cs": "Check the PHP files for code style violations and best practices, ignoring warnings.", "check-cs-errors": "Alias for check-cs script.", - "fix-cs": "Auto-fix code style violations in the PHP files." + "check-cs-warnings": "Check the PHP files for code style violations and best practices, including warnings.", + "check-staged-cs": "Check the staged PHP files for code style violations and best practices.", + "check-branch-cs": "Check the PHP files changed in the current branch for code style violations and best practices.", + "fix-cs": "Auto-fix code style violations in the PHP files.", + "test": "Run the unit tests without code coverage.", + "coverage": "Run the unit tests with code coverage." } } diff --git a/config/composer/actions.php b/config/composer/actions.php new file mode 100644 index 0000000..e897d08 --- /dev/null +++ b/config/composer/actions.php @@ -0,0 +1,189 @@ +getIO(); + + $choices = [ + '1' => [ + 'label' => 'Check staged files for coding standard warnings & errors.', + 'command' => 'check-staged-cs', + ], + '2' => [ + 'label' => 'Check current branch\'s changed files for coding standard warnings & errors.', + 'command' => 'check-branch-cs', + ], + '3' => [ + 'label' => 'Check for all coding standard errors.', + 'command' => 'check-cs', + ], + '4' => [ + 'label' => 'Check for all coding standard warnings & errors.', + 'command' => 'check-cs-warnings', + ], + '5' => [ + 'label' => 'Fix auto-fixable coding standards.', + 'command' => 'fix-cs', + ], + '6' => [ + 'label' => 'Verify coding standard violations are below thresholds.', + 'command' => 'check-cs-thresholds', + ], + ]; + + $args = $event->getArguments(); + if ( empty( $args ) ) { + foreach ( $choices as $choice => $data ) { + $io->write( \sprintf( '%d. %s', $choice, $data['label'] ) ); + } + + $choice = $io->ask( 'What do you want to do? ' ); + } + else { + $choice = $args[0]; + } + + if ( isset( $choices[ $choice ] ) ) { + $event_dispatcher = $event->getComposer()->getEventDispatcher(); + $event_dispatcher->dispatchScript( $choices[ $choice ]['command'] ); + } + else { + $io->write( 'Unknown choice.' ); + } + } + + /** + * Runs PHPCS on the files changed in the current branch. + * + * Used by the composer check-branch-cs command. + * + * @codeCoverageIgnore + * + * @param Event $event Composer event that triggered this script. + * + * @return void + */ + public static function check_branch_cs( Event $event ) { + $branch = 'develop'; + + $args = $event->getArguments(); + if ( ! empty( $args ) ) { + $branch = $args[0]; + } + + exit( self::check_cs_for_changed_files( $branch ) ); + } + + /** + * Runs PHPCS on changed files compared to some git reference. + * + * @codeCoverageIgnore + * + * @param string $compare The git reference. + * + * @return int Exit code passed from the coding standards check. + */ + private static function check_cs_for_changed_files( $compare ) { + \exec( 'git diff --name-only --diff-filter=d ' . \escapeshellarg( $compare ), $files ); + + $php_files = self::filter_files( $files, '.php' ); + if ( empty( $php_files ) ) { + echo 'No files to compare! Exiting.' . \PHP_EOL; + + return 0; + } + + /* + * In CI, generate both the normal report as well as the checkstyle report. + * The normal report will be shown in the actions output and ensures human readable (and colorized!) results there. + * The checkstyle report is used to show the results inline in the GitHub code view. + */ + $extra_args = ( \getenv( 'CI' ) === false ) ? '' : ' --colors --no-cache --report-full --report-checkstyle=./phpcs-report.xml'; + $command = \sprintf( + 'composer check-cs-warnings -- %s %s', + \implode( ' ', \array_map( 'escapeshellarg', $php_files ) ), + $extra_args + ); + \system( $command, $exit_code ); + + return $exit_code; + } + + /** + * Checks if the CS errors and warnings are below or at thresholds. + * + * @return void + */ + public static function check_cs_thresholds() { + $in_ci = \getenv( 'CI' ); + + echo 'Running coding standards checks, this may take some time.', \PHP_EOL; + + $command = 'composer check-cs-warnings -- -mq --report="YoastCS\\Yoast\\Reports\\Threshold"'; + if ( $in_ci !== false ) { + // Always show the results in CI in color. + $command .= ' --colors'; + } + // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged -- Non-WP context, this is fine. + @\exec( $command, $phpcs_output, $return ); + + $phpcs_output = \implode( \PHP_EOL, $phpcs_output ); + echo $phpcs_output; + + $above_threshold = true; + if ( \strpos( $phpcs_output, 'Coding standards checks have passed!' ) !== false ) { + $above_threshold = false; + } + + /* + * Don't run the branch check in CI/GH Actions as it prevents the errors from being shown inline. + * The GH Actions script will run this via a separate script step. + */ + if ( $above_threshold === true && $in_ci === false ) { + echo \PHP_EOL; + echo 'Running check-branch-cs.', \PHP_EOL; + echo 'This might show problems on untouched lines. Focus on the lines you\'ve changed first.', \PHP_EOL; + echo \PHP_EOL; + + // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged -- Non-WP context, this is fine. + @\passthru( 'composer check-branch-cs' ); + } + + exit( ( $above_threshold === true || $return > 2 ) ? $return : 0 ); + } + + /** + * Filter files on extension. + * + * @param array $files List of files. + * @param string $extension Extension to filter on. + * + * @return array Filtered list of files. + */ + private static function filter_files( array $files, string $extension ): array { + return \array_filter( + $files, + static function ( $file ) use ( $extension ) { + return \substr( $file, ( 0 - \strlen( $extension ) ) ) === $extension; + } + ); + } +}