From ec33ecbada9b0c66f93b1e7cdb5c96dd98ef6388 Mon Sep 17 00:00:00 2001 From: Andrew Nicols Date: Wed, 14 Feb 2024 13:03:52 +0800 Subject: [PATCH] Unit test classes should be declared as either final or abstract Fixes #102 --- .../Sniffs/PHPUnit/TestClassesFinalSniff.php | 85 +++++++++++++++++++ .../PHPUnit/TestClassesFinalSniffTest.php | 67 +++++++++++++++ .../PHPUnit/fixtures/testclassesfinal.php | 21 +++++ .../fixtures/testclassesfinal.php.fixed | 21 +++++ 4 files changed, 194 insertions(+) create mode 100644 moodle/Sniffs/PHPUnit/TestClassesFinalSniff.php create mode 100644 moodle/Tests/Sniffs/PHPUnit/TestClassesFinalSniffTest.php create mode 100644 moodle/Tests/Sniffs/PHPUnit/fixtures/testclassesfinal.php create mode 100644 moodle/Tests/Sniffs/PHPUnit/fixtures/testclassesfinal.php.fixed diff --git a/moodle/Sniffs/PHPUnit/TestClassesFinalSniff.php b/moodle/Sniffs/PHPUnit/TestClassesFinalSniff.php new file mode 100644 index 0000000..45ca214 --- /dev/null +++ b/moodle/Sniffs/PHPUnit/TestClassesFinalSniff.php @@ -0,0 +1,85 @@ +. + +namespace MoodleHQ\MoodleCS\moodle\Sniffs\PHPUnit; + +// phpcs:disable moodle.NamingConventions + +use MoodleHQ\MoodleCS\moodle\Util\MoodleUtil; +use PHP_CodeSniffer\Sniffs\Sniff; +use PHP_CodeSniffer\Files\File; +use PHPCSUtils\Utils\ObjectDeclarations; + +/** + * Checks that test classes are declared either abstract or final. + * + * @package local_codechecker + * @copyright 2024 Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class TestClassesFinalSniff implements Sniff { + public function register() { + return [ + T_OPEN_TAG, + ]; + } + + public function process(File $file, $pointer) { + // If the file is not a unit test file, nothing to check. + if (!MoodleUtil::isUnitTest($file) && !MoodleUtil::isUnitTestRunning()) { + return; // @codeCoverageIgnore + } + + // Get the file tokens, for ease of use. + $tokens = $file->getTokens(); + + // We only want to do this once per file. + $prevopentag = $file->findPrevious(T_OPEN_TAG, $pointer - 1); + if ($prevopentag !== false) { + return; // @codeCoverageIgnore + } + + $cStart = $pointer; + while ($cStart = $file->findNext(T_CLASS, $cStart + 1)) { + if (MoodleUtil::isUnitTestCaseClass($file, $cStart) === false) { + // This class does not relate to a unit test. + continue; + } + $className = ObjectDeclarations::getName($file, $cStart); + + if (substr($className, -5) !== '_test') { + continue; + } + + $classInfo = ObjectDeclarations::getClassProperties($file, $cStart); + + if (!$classInfo['is_abstract'] && $classInfo['is_final'] === false) { + $fix = $file->addFixableWarning( + 'Unit test %s should be declared as final.', + $cStart, + 'UnitTestClassesFinal', + [$className], + ); + + if ($fix) { + $file->fixer->beginChangeset(); + $file->fixer->addContentBefore($cStart, 'final '); + $file->fixer->endChangeset(); + } + } + } + } +} diff --git a/moodle/Tests/Sniffs/PHPUnit/TestClassesFinalSniffTest.php b/moodle/Tests/Sniffs/PHPUnit/TestClassesFinalSniffTest.php new file mode 100644 index 0000000..59c02fe --- /dev/null +++ b/moodle/Tests/Sniffs/PHPUnit/TestClassesFinalSniffTest.php @@ -0,0 +1,67 @@ +. + +namespace MoodleHQ\MoodleCS\moodle\Tests\Sniffs\PHPUnit; + +use MoodleHQ\MoodleCS\moodle\Tests\MoodleCSBaseTestCase; + +// phpcs:disable moodle.NamingConventions + +/** + * Test the TestClassesFinalSniff sniff. + * + * @package local_codechecker + * @category test + * @copyright 2024 Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * + * @covers \MoodleHQ\MoodleCS\moodle\Sniffs\PHPUnit\TestClassesFinalSniff + */ +class TestclassesFinalSniffTest extends MoodleCSBaseTestCase { + + /** + * Data provider for self::provider_phpunit_data_returntypes + */ + public static function phpunit_classes_final_provider(): array { + return [ + 'Standard fixes' => [ + 'fixture' => 'testclassesfinal', + 'errors' => [ + ], + 'warnings' => [ + 16 => 'Unit test example_standard_test should be declared as final.', + ], + ], + ]; + } + + /** + * @dataProvider phpunit_classes_final_provider + */ + public function test_phpunit_classes_final( + string $fixture, + array $errors, + array $warnings + ): void { + $this->set_standard('moodle'); + $this->set_sniff('moodle.PHPUnit.TestClassesFinal'); + $this->set_fixture(sprintf("%s/fixtures/%s.php", __DIR__, $fixture)); + $this->set_warnings($warnings); + $this->set_errors($errors); + + $this->verify_cs_results(); + } +} diff --git a/moodle/Tests/Sniffs/PHPUnit/fixtures/testclassesfinal.php b/moodle/Tests/Sniffs/PHPUnit/fixtures/testclassesfinal.php new file mode 100644 index 0000000..3c181fe --- /dev/null +++ b/moodle/Tests/Sniffs/PHPUnit/fixtures/testclassesfinal.php @@ -0,0 +1,21 @@ +