diff --git a/CHANGELOG.md b/CHANGELOG.md index a517c8a..32f67dc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ This project adheres to [Semantic Versioning](https://semver.org/). The format of this change log follows the advice given at [Keep a CHANGELOG](https://keepachangelog.com). ## [Unreleased] +### Added +- Add new `moodle.Commenting.ConstructorReturn` sniff to check that constructors do not document a return value. ## [v3.4.6] - 2024-04-03 ### Fixed diff --git a/moodle/Sniffs/Commenting/ConstructorReturnSniff.php b/moodle/Sniffs/Commenting/ConstructorReturnSniff.php new file mode 100644 index 0000000..71de2fe --- /dev/null +++ b/moodle/Sniffs/Commenting/ConstructorReturnSniff.php @@ -0,0 +1,128 @@ +. + +namespace MoodleHQ\MoodleCS\moodle\Sniffs\Commenting; + +use MoodleHQ\MoodleCS\moodle\Util\Docblocks; +use MoodleHQ\MoodleCS\moodle\Util\TokenUtil; +use PHP_CodeSniffer\Files\File; +use PHP_CodeSniffer\Sniffs\Sniff; + +/** + * Checks that all files an classes have appropriate docs. + * + * @copyright 2024 Andrew Lyons + * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class ConstructorReturnSniff implements Sniff +{ + /** + * Register for class tags. + */ + public function register() { + + return [ + T_CLASS, + ]; + } + + /** + * Processes php files and perform various checks with file. + * + * @param File $phpcsFile The file being scanned. + * @param int $stackPtr The position in the stack. + */ + public function process(File $phpcsFile, $stackPtr) { + $tokens = $phpcsFile->getTokens(); + $endClassPtr = $tokens[$stackPtr]['scope_closer']; + + while ( + ($methodPtr = $phpcsFile->findNext(T_FUNCTION, $stackPtr + 1, $endClassPtr)) !== false + ) { + $this->processClassMethod($phpcsFile, $methodPtr); + $stackPtr = $methodPtr; + } + } + + /** + * Processes the class method. + * + * @param File $phpcsFile The file being scanned. + * @param int $stackPtr The position in the stack. + */ + protected function processClassMethod(File $phpcsFile, int $stackPtr): void { + $objectName = TokenUtil::getObjectName($phpcsFile, $stackPtr); + if ($objectName !== '__constructor') { + // We only care about constructors. + return; + } + + // Get docblock. + $docblockPtr = Docblocks::getDocBlockPointer($phpcsFile, $stackPtr); + if ($docblockPtr === null) { + // No docblocks for this constructor. + return; + } + + $returnTokens = Docblocks::getMatchingDocTags($phpcsFile, $docblockPtr, '@return'); + if (count($returnTokens) === 0) { + // No @return tag in the docblock. + return; + } + + $fix = $phpcsFile->addFixableError( + 'Constructor should not have a return tag in the docblock', + $returnTokens[0], + 'ConstructorReturn' + ); + if ($fix) { + $tokens = $phpcsFile->getTokens(); + $phpcsFile->fixer->beginChangeset(); + + // Find the tokens at the start and end of the line. + $lineStart = $phpcsFile->findFirstOnLine(T_DOC_COMMENT_STAR, $returnTokens[0]); + if ($lineStart === false) { + $lineStart = $returnTokens[0]; + } + + $ptr = $phpcsFile->findNext(T_DOC_COMMENT_WHITESPACE, $lineStart); + for ($lineEnd = $lineStart; $lineEnd < $tokens[$docblockPtr]['comment_closer']; $lineEnd++) { + if ($tokens[$lineEnd]['line'] !== $tokens[$lineStart]['line']) { + break; + } + } + + if ($tokens[$lineEnd]['code'] === T_DOC_COMMENT_CLOSE_TAG) { + $lineEnd--; + } + + // if ($tokens[$lineStart]['code'] === T_DOC_COMMENT_OPEN_TAG) { + // if ($tokens[$lineEnd]['code'] !== T_DOC_COMMENT_CLOSE_TAG) { + // $lineStart = $phpcsFile->findNext(T_DOC_COMMENT_WHITESPACE, $returnTokens[0]); + // } + // } elseif ($tokens[$lineEnd]['code'] === T_DOC_COMMENT_CLOSE_TAG) { + // $lineEnd--; + // } + + for ($ptr = $lineStart; $ptr <= $lineEnd; $ptr++) { + $phpcsFile->fixer->replaceToken($ptr, ''); + } + + $phpcsFile->fixer->endChangeset(); + } + } +} diff --git a/moodle/Tests/Sniffs/Commenting/ConstructorReturnSniffTest.php b/moodle/Tests/Sniffs/Commenting/ConstructorReturnSniffTest.php new file mode 100644 index 0000000..8ca07c4 --- /dev/null +++ b/moodle/Tests/Sniffs/Commenting/ConstructorReturnSniffTest.php @@ -0,0 +1,64 @@ +. + +namespace MoodleHQ\MoodleCS\moodle\Tests\Sniffs\Commenting; + +use MoodleHQ\MoodleCS\moodle\Tests\MoodleCSBaseTestCase; + +/** + * Test the ConstructorReturnSniff sniff. + * + * @copyright 2024 onwards Andrew Lyons + * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * + * @covers \MoodleHQ\MoodleCS\moodle\Sniffs\Commenting\MissingDocblockSniff + */ +class ConstructorReturnSniffTest extends MoodleCSBaseTestCase +{ + /** + * @dataProvider docblockCorrectnessProvider + */ + public function testMissingDocblockSniff( + string $fixture, + array $errors, + array $warnings + ): void { + $this->setStandard('moodle'); + $this->setSniff('moodle.Commenting.ConstructorReturn'); + $this->setFixture(sprintf("%s/fixtures/ConstructorReturn/%s.php", __DIR__, $fixture)); + $this->setWarnings($warnings); + $this->setErrors($errors); + + $this->verifyCsResults(); + } + + public static function docblockCorrectnessProvider(): array { + $cases = [ + [ + 'fixture' => 'general', + 'errors' => [ + 43 => 'Constructor should not have a return tag in the docblock', + 52 => 'Constructor should not have a return tag in the docblock', + 60 => 'Constructor should not have a return tag in the docblock', + 71 => 'Constructor should not have a return tag in the docblock', + ], + 'warnings' => [], + ], + ]; + return $cases; + } +} diff --git a/moodle/Tests/Sniffs/Commenting/fixtures/ConstructorReturn/general.php b/moodle/Tests/Sniffs/Commenting/fixtures/ConstructorReturn/general.php new file mode 100644 index 0000000..2207678 --- /dev/null +++ b/moodle/Tests/Sniffs/Commenting/fixtures/ConstructorReturn/general.php @@ -0,0 +1,76 @@ +