Skip to content

Commit

Permalink
Add sniff to detect missing docblocks
Browse files Browse the repository at this point in the history
  • Loading branch information
andrewnicols committed Mar 14, 2024
1 parent 91cbead commit c56ca09
Show file tree
Hide file tree
Showing 14 changed files with 525 additions and 15 deletions.
124 changes: 124 additions & 0 deletions moodle/Sniffs/Commenting/MissingDocblockSniff.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
<?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\Tokens;
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 MissingDocblockSniff implements Sniff
{
/**
* Register for open tag (only process once per file).
*/
public function register() {
return [
T_OPEN_TAG,
];
}

/**
* 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();

// Each class, interface, trait, and enum must have a docblock.
// If a file has one class, interface, trait, or enum, the file docblock is optinoal.
// Otherwise, the file docblock is required.

// First find out how many items there are.
$find = [
// Classes, interfaces, and traits.
T_CLASS,
T_INTERFACE,
T_TRAIT,

// Functions outside of those.
T_FUNCTION,
];

if (version_compare(PHP_VERSION, '8.1.0') >= 0) {
$find[] = T_ENUM;
}

$artefactCount = 0;
$missingDocblocks = [];

$typePtr = $stackPtr + 1;
while ($typePtr = $phpcsFile->findNext($find, $typePtr + 1)) {
$token = $tokens[$typePtr];
if ($token['code'] === T_FUNCTION && !empty($token['conditions'])) {
// Skip methods of classes, traits and interfaces.
continue;
}

$artefactCount++;

if ($docblock = Docblocks::getDocBlock($phpcsFile, $typePtr)) {
// There should be no empty lines between the artefact and the docblock.
$lastline = $tokens[$docblock['comment_closer']]['line'];
for ($interimPtr = $docblock['comment_closer'] + 1; $interimPtr < $typePtr; $interimPtr++) {
if ($tokens[$interimPtr]['code'] === T_ATTRIBUTE) {
$interimPtr = $tokens[$interimPtr]['attribute_closer'];
$lastline = $tokens[$interimPtr]['line'];
continue;
}
if ($tokens[$interimPtr]['line'] > $lastline) {
$missingDocblocks[] = $typePtr;
break;
}
}
} else {
$missingDocblocks[] = $typePtr;
}
}

if ($artefactCount !== 1) {
// See if there is a file docblock.
$fileblock = Docblocks::getDocBlock($phpcsFile, $stackPtr);

if ($fileblock === null) {
$objectName = Tokens::getObjectName($phpcsFile, $stackPtr);
$phpcsFile->addError('Missing docblock for file %s', $stackPtr, 'Missing', [$objectName]);
}
}

foreach ($missingDocblocks as $typePtr) {
$objectName = Tokens::getObjectName($phpcsFile, $typePtr);
$objectType = Tokens::getObjectType($phpcsFile, $typePtr);
$phpcsFile->addError('Missing docblock for %s %s', $typePtr, 'Missing', [$objectType, $objectName]);
}

if ($artefactCount === 1) {
// Only one artefact.
// No need for file docblock.
return;
}
}
}
4 changes: 0 additions & 4 deletions moodle/Sniffs/Commenting/PackageSniff.php
Original file line number Diff line number Diff line change
Expand Up @@ -78,10 +78,6 @@ public function process(File $phpcsFile, $stackPtr) {
$docblock = Docblocks::getDocBlock($phpcsFile, $typePtr);

if ($docblock === null) {
$objectName = $this->getObjectName($phpcsFile, $typePtr);
$objectType = $this->getObjectType($phpcsFile, $typePtr);
$phpcsFile->addError('Missing doc comment for %s %s', $typePtr, 'Missing', [$objectType, $objectName]);

continue;
}

Expand Down
122 changes: 122 additions & 0 deletions moodle/Tests/Sniffs/Commenting/MissingDocblockSniffTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
<?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 MissingDocblockSniff 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 MissingDocblockSniffTest extends MoodleCSBaseTestCase
{
/**
* @dataProvider docblockCorrectnessProvider
*/
public function testMissingDocblockSnfif(
string $fixture,
array $errors,
array $warnings
): void {
$this->setStandard('moodle');
$this->setSniff('moodle.Commenting.MissingDocblock');
$this->setFixture(sprintf("%s/fixtures/%s.php", __DIR__, $fixture));
$this->setWarnings($warnings);
$this->setErrors($errors);
$this->setComponentMapping([
'local_codechecker' => dirname(__DIR__),
]);

$this->verifyCsResults();
}

public static function docblockCorrectnessProvider(): array {
$cases = [
'Multiple artefacts in a file' => [
'fixture' => 'missing_docblock_multiple_artefacts',
'errors' => [
1 => 'Missing docblock for file missing_docblock_multiple_artefacts.php',
34 => 'Missing docblock for function missing_docblock_in_function',
38 => 'Missing docblock for class missing_docblock_in_class',
95 => 'Missing docblock for interface missing_docblock_interface',
118 => 'Missing docblock for trait missing_docblock_trait', ],
'warnings' => [],
],
'File level tag, no class' => [
'fixture' => 'missing_docblock_class_without_docblock',
'errors' => [
11 => 'Missing docblock for class class_without_docblock',
],
'warnings' => [],
],
'Class only (incorrect whitespace)' => [
'fixture' => 'missing_docblock_class_only_with_incorrect_whitespace',
'errors' => [
11 => 'Missing docblock for class class_only_with_incorrect_whitespace',
],
'warnings' => [],
],
'Class only (correct)' => [
'fixture' => 'missing_docblock_class_only',
'errors' => [],
'warnings' => [],
],
'Class only with attributes (correct)' => [
'fixture' => 'missing_docblock_class_only_with_attributes',
'errors' => [],
'warnings' => [],
],
'Class only with attributes and incorrect whitespace' => [
'fixture' => 'missing_docblock_class_only_with_attributes_incorrect_whitespace',
'errors' => [
13 => 'Missing docblock for class class_only_with_attributes_incorrect_whitespace',
],
'warnings' => [],
],
'Class and file (correct)' => [
'fixture' => 'missing_docblock_class_and_file',
'errors' => [],
'warnings' => [],
],
'Interface only (correct)' => [
'fixture' => 'missing_docblock_interface_only',
'errors' => [],
'warnings' => [],
],
'Trait only (correct)' => [
'fixture' => 'missing_docblock_trait_only',
'errors' => [],
'warnings' => [],
],
];

if (version_compare(PHP_VERSION, '8.1.0') >= 0) {
$cases['Enum only (correct)'] = [
'fixture' => 'missing_docblock_enum_only',
'errors' => [],
'warnings' => [],
];
}

return $cases;
}
}
12 changes: 1 addition & 11 deletions moodle/Tests/Sniffs/Commenting/PackageSniffTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,7 @@ public function testPackageOnMissingComponent(): void {
$this->setComponentMapping([]); // No components available.

$this->setWarnings([]);
$this->setErrors([
// These are still checked because this doesn't depend on the - missing - component mapping.
35 => 'Missing doc comment for class missing_docblock_in_class',
38 => 'Missing doc comment for interface missing_docblock_in_interface',
41 => 'Missing doc comment for trait missing_docblock_in_trait',
44 => 'Missing doc comment for function missing_docblock_in_function',
]);
$this->setErrors([]);

$this->verifyCsResults();
}
Expand Down Expand Up @@ -77,8 +71,6 @@ public static function packageCorrectnessProvider(): array {
'errors' => [
18 => 'DocBlock missing a @package tag for function package_missing. Expected @package local_codechecker',
31 => 'DocBlock missing a @package tag for class package_absent. Expected @package local_codechecker',
34 => 'Missing doc comment for function missing_docblock_in_function',
38 => 'Missing doc comment for class missing_docblock_in_class',
42 => '@package tag for function package_wrong_in_function. Expected local_codechecker, found wrong_package.',
48 => '@package tag for class package_wrong_in_class. Expected local_codechecker, found wrong_package.',
57 => 'More than one @package tag found in function package_multiple_in_function',
Expand All @@ -87,10 +79,8 @@ public static function packageCorrectnessProvider(): array {
78 => 'More than one @package tag found in class package_multiple_in_class_all_wrong',
85 => 'More than one @package tag found in interface package_multiple_in_interface_all_wrong',
92 => 'More than one @package tag found in trait package_multiple_in_trait_all_wrong',
95 => 'Missing doc comment for interface missing_docblock_interface',
101 => 'missing a @package tag for interface missing_package_interface. Expected @package',
106 => '@package tag for interface incorrect_package_interface. Expected local_codechecker, found',
118 => 'Missing doc comment for trait missing_docblock_trait',
124 => 'DocBlock missing a @package tag for trait missing_package_trait. Expected @package',
129 => 'Incorrect @package tag for trait incorrect_package_trait. Expected local_codechecker, found',
],
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

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

/**
* File level docblock.
*/

use example;

/**
* Class level docblock.
*/
class class_only {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

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

use example;

/**
* Class level docblock.
*/
class class_only {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

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

use example;

/**
* Class level docblock.
*/
#[example_attribute]
#[with_multiple_attributes, and_another_attribute]
class class_with_docblock_and_attributes {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php

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

use example;

/**
* Class level docblock.
*/
#[example_attribute]
#[with_multiple_attributes, and_another_attribute]

class class_only_with_attributes_incorrect_whitespace {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

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

use example;

/**
* Class level docblock but incorrect whitespace.
*/

class class_only_with_incorrect_whitespace {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

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

/**
* File level tag
*/

use example;

class class_without_docblock {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

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

use example;

/**
* Enum level docblock.
*/
enum class_only {
}
Loading

0 comments on commit c56ca09

Please sign in to comment.