Skip to content

Commit

Permalink
Add new sniff to detect and remove constructor @return docs (#157)
Browse files Browse the repository at this point in the history
  • Loading branch information
andrewnicols committed Apr 17, 2024
1 parent 1ec84f4 commit a0d7eb1
Show file tree
Hide file tree
Showing 5 changed files with 344 additions and 0 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
128 changes: 128 additions & 0 deletions moodle/Sniffs/Commenting/ConstructorReturnSniff.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
<?php

// This file is part of Moodle - https://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANdTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <https://www.gnu.org/licenses/>.

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 <andrew@nicols.co.uk>
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class ConstructorReturnSniff implements Sniff
{
/**
* Register for class tags.
*/
public function register() {

Check warning on line 36 in moodle/Sniffs/Commenting/ConstructorReturnSniff.php

View check run for this annotation

Codecov / codecov/patch

moodle/Sniffs/Commenting/ConstructorReturnSniff.php#L36

Added line #L36 was not covered by tests

return [
T_CLASS,
];

Check warning on line 40 in moodle/Sniffs/Commenting/ConstructorReturnSniff.php

View check run for this annotation

Codecov / codecov/patch

moodle/Sniffs/Commenting/ConstructorReturnSniff.php#L38-L40

Added lines #L38 - L40 were not covered by tests
}

/**
* 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'];

Check warning on line 51 in moodle/Sniffs/Commenting/ConstructorReturnSniff.php

View check run for this annotation

Codecov / codecov/patch

moodle/Sniffs/Commenting/ConstructorReturnSniff.php#L49-L51

Added lines #L49 - L51 were not covered by tests

while (
($methodPtr = $phpcsFile->findNext(T_FUNCTION, $stackPtr + 1, $endClassPtr)) !== false

Check warning on line 54 in moodle/Sniffs/Commenting/ConstructorReturnSniff.php

View check run for this annotation

Codecov / codecov/patch

moodle/Sniffs/Commenting/ConstructorReturnSniff.php#L54

Added line #L54 was not covered by tests
) {
$this->processClassMethod($phpcsFile, $methodPtr);
$stackPtr = $methodPtr;

Check warning on line 57 in moodle/Sniffs/Commenting/ConstructorReturnSniff.php

View check run for this annotation

Codecov / codecov/patch

moodle/Sniffs/Commenting/ConstructorReturnSniff.php#L56-L57

Added lines #L56 - L57 were not covered by tests
}
}

/**
* 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') {

Check warning on line 69 in moodle/Sniffs/Commenting/ConstructorReturnSniff.php

View check run for this annotation

Codecov / codecov/patch

moodle/Sniffs/Commenting/ConstructorReturnSniff.php#L67-L69

Added lines #L67 - L69 were not covered by tests
// We only care about constructors.
return;

Check warning on line 71 in moodle/Sniffs/Commenting/ConstructorReturnSniff.php

View check run for this annotation

Codecov / codecov/patch

moodle/Sniffs/Commenting/ConstructorReturnSniff.php#L71

Added line #L71 was not covered by tests
}

// Get docblock.
$docblockPtr = Docblocks::getDocBlockPointer($phpcsFile, $stackPtr);
if ($docblockPtr === null) {

Check warning on line 76 in moodle/Sniffs/Commenting/ConstructorReturnSniff.php

View check run for this annotation

Codecov / codecov/patch

moodle/Sniffs/Commenting/ConstructorReturnSniff.php#L75-L76

Added lines #L75 - L76 were not covered by tests
// No docblocks for this constructor.
return;

Check warning on line 78 in moodle/Sniffs/Commenting/ConstructorReturnSniff.php

View check run for this annotation

Codecov / codecov/patch

moodle/Sniffs/Commenting/ConstructorReturnSniff.php#L78

Added line #L78 was not covered by tests
}

$returnTokens = Docblocks::getMatchingDocTags($phpcsFile, $docblockPtr, '@return');
if (count($returnTokens) === 0) {

Check warning on line 82 in moodle/Sniffs/Commenting/ConstructorReturnSniff.php

View check run for this annotation

Codecov / codecov/patch

moodle/Sniffs/Commenting/ConstructorReturnSniff.php#L81-L82

Added lines #L81 - L82 were not covered by tests
// No @return tag in the docblock.
return;

Check warning on line 84 in moodle/Sniffs/Commenting/ConstructorReturnSniff.php

View check run for this annotation

Codecov / codecov/patch

moodle/Sniffs/Commenting/ConstructorReturnSniff.php#L84

Added line #L84 was not covered by tests
}

$fix = $phpcsFile->addFixableError(
'Constructor should not have a return tag in the docblock',
$returnTokens[0],
'ConstructorReturn'

Check warning on line 90 in moodle/Sniffs/Commenting/ConstructorReturnSniff.php

View check run for this annotation

Codecov / codecov/patch

moodle/Sniffs/Commenting/ConstructorReturnSniff.php#L87-L90

Added lines #L87 - L90 were not covered by tests
);
if ($fix) {
$tokens = $phpcsFile->getTokens();
$phpcsFile->fixer->beginChangeset();

Check warning on line 94 in moodle/Sniffs/Commenting/ConstructorReturnSniff.php

View check run for this annotation

Codecov / codecov/patch

moodle/Sniffs/Commenting/ConstructorReturnSniff.php#L92-L94

Added lines #L92 - L94 were not covered by tests

// 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];

Check warning on line 99 in moodle/Sniffs/Commenting/ConstructorReturnSniff.php

View check run for this annotation

Codecov / codecov/patch

moodle/Sniffs/Commenting/ConstructorReturnSniff.php#L97-L99

Added lines #L97 - L99 were not covered by tests
}

$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;

Check warning on line 105 in moodle/Sniffs/Commenting/ConstructorReturnSniff.php

View check run for this annotation

Codecov / codecov/patch

moodle/Sniffs/Commenting/ConstructorReturnSniff.php#L102-L105

Added lines #L102 - L105 were not covered by tests
}
}

if ($tokens[$lineEnd]['code'] === T_DOC_COMMENT_CLOSE_TAG) {
$lineEnd--;

Check warning on line 110 in moodle/Sniffs/Commenting/ConstructorReturnSniff.php

View check run for this annotation

Codecov / codecov/patch

moodle/Sniffs/Commenting/ConstructorReturnSniff.php#L109-L110

Added lines #L109 - L110 were not covered by tests
}

// 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, '');

Check warning on line 122 in moodle/Sniffs/Commenting/ConstructorReturnSniff.php

View check run for this annotation

Codecov / codecov/patch

moodle/Sniffs/Commenting/ConstructorReturnSniff.php#L121-L122

Added lines #L121 - L122 were not covered by tests
}

$phpcsFile->fixer->endChangeset();

Check warning on line 125 in moodle/Sniffs/Commenting/ConstructorReturnSniff.php

View check run for this annotation

Codecov / codecov/patch

moodle/Sniffs/Commenting/ConstructorReturnSniff.php#L125

Added line #L125 was not covered by tests
}
}
}
64 changes: 64 additions & 0 deletions moodle/Tests/Sniffs/Commenting/ConstructorReturnSniffTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
<?php

// This file is part of Moodle - https://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <https://www.gnu.org/licenses/>.

namespace MoodleHQ\MoodleCS\moodle\Tests\Sniffs\Commenting;

use MoodleHQ\MoodleCS\moodle\Tests\MoodleCSBaseTestCase;

/**
* Test the ConstructorReturnSniff sniff.
*
* @copyright 2024 onwards Andrew Lyons <andrew@nicols.co.uk>
* @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;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
<?php

namespace MoodleHQ\MoodleCS\moodle\Tests\Sniffs\PHPUnit;

/**
* No docblocks on constructor.
*/
class NoDocblockOnConstructor {
public function __constructor() {
return;
}
}

class DocBlockOnNonConstructor {
public function __constructor() {
return;
}

/**
* @return void
*/
public function non__constructor() {
return;
}
}

class DocBlockOnConstructorNoReturn {
/**
* Example constructor docblock
*
* @param string $example
*/
public function __constructor(string $example) {
return;
}
}

class DocBlockOnConstructorHasReturn {
/**
* Example constructor docblock
*
* @param string $example Some value
* @return void
* @todo This is a todo
*/
public function __constructor(string $example) {
return;
}
}

class DocBlockOnConstructorHasInlineReturn {
/** @return self */
public function __constructor(string $example) {
return;
}
}

class DocBlockOnConstructorHasNearlyInlineReturn {
/**
* @return self */
public function __constructor(string $example) {
return;
}
}

class DocBlockOnConstructorHasReturnNoExtraTag {
/**
* Example constructor docblock
*
* @param string $example Some value
* @return void
*/
public function __constructor(string $example) {
return;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
<?php

namespace MoodleHQ\MoodleCS\moodle\Tests\Sniffs\PHPUnit;

/**
* No docblocks on constructor.
*/
class NoDocblockOnConstructor {
public function __constructor() {
return;
}
}

class DocBlockOnNonConstructor {
public function __constructor() {
return;
}

/**
* @return void
*/
public function non__constructor() {
return;
}
}

class DocBlockOnConstructorNoReturn {
/**
* Example constructor docblock
*
* @param string $example
*/
public function __constructor(string $example) {
return;
}
}

class DocBlockOnConstructorHasReturn {
/**
* Example constructor docblock
*
* @param string $example Some value
* @todo This is a todo
*/
public function __constructor(string $example) {
return;
}
}

class DocBlockOnConstructorHasInlineReturn {
/** */
public function __constructor(string $example) {
return;
}
}

class DocBlockOnConstructorHasNearlyInlineReturn {
/**
*/
public function __constructor(string $example) {
return;
}
}

class DocBlockOnConstructorHasReturnNoExtraTag {
/**
* Example constructor docblock
*
* @param string $example Some value
*/
public function __constructor(string $example) {
return;
}
}

0 comments on commit a0d7eb1

Please sign in to comment.