Skip to content

Commit

Permalink
Check the Category tag against APIs
Browse files Browse the repository at this point in the history
  • Loading branch information
andrewnicols committed Feb 19, 2024
1 parent a0ffbd9 commit fc9b453
Show file tree
Hide file tree
Showing 7 changed files with 381 additions and 0 deletions.
82 changes: 82 additions & 0 deletions moodle/Sniffs/Commenting/CategorySniff.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
<?php
// This file is part of Moodle - http://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 <http://www.gnu.org/licenses/>.

namespace MoodleHQ\MoodleCS\moodle\Sniffs\Commenting;

// phpcs:disable moodle.NamingConventions

use MoodleHQ\MoodleCS\moodle\Util\MoodleUtil;
use MoodleHQ\MoodleCS\moodle\Util\Docblocks;
use PHP_CodeSniffer\Sniffs\Sniff;
use PHP_CodeSniffer\Files\File;

/**
* Checks that all test classes and global functions have appropriate @package tags.
*
* @copyright 2024 Andrew Lyons <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class CategorySniff implements Sniff {

/**
* Register for open tag (only process once per file).
*/
public function register() {
return [
T_DOC_COMMENT_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) {
// The apis.json was introduced in Moodle 4.02.
// Make and exception for codechecker phpunit tests, so they are run always.
if (!MoodleUtil::meetsMinimumMoodleVersion($phpcsFile, 402) && !MoodleUtil::isUnitTestRunning()) {
return; // @codeCoverageIgnore
}

$categoryTokens = Docblocks::getMatchingDocTags($phpcsFile, $stackPtr, '@category');
if (empty($categoryTokens)) {
return;
}

$tokens = $phpcsFile->getTokens();
$apis = MoodleUtil::getMoodleApis($phpcsFile);

$docblock = Docblocks::getDocBlock($phpcsFile, $stackPtr);
foreach ($categoryTokens as $tokenPtr) {
$categoryValuePtr = $phpcsFile->findNext(
T_DOC_COMMENT_STRING,
$tokenPtr,
$docblock['comment_closer']
);
$categoryValue = $tokens[$categoryValuePtr]['content'];
if (!in_array($categoryValue, $apis)) {
$phpcsFile->addError(
'Invalid @category tag value "%s".',
$categoryValuePtr,
'Invalid',
[$categoryValue]
);
}
}
}
}
4 changes: 4 additions & 0 deletions moodle/Tests/MoodleCSBaseTestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,10 @@ public function set_component_mapping(array $mapping): void {
\MoodleHQ\MoodleCS\moodle\Util\MoodleUtil::setMockedComponentMappings($mapping);
}

public function set_api_mapping(array $mapping): void {
\MoodleHQ\MoodleCS\moodle\Util\MoodleUtil::setMockedApiMappings($mapping);
}

/**
* Set the name of the standard to be tested.
*
Expand Down
70 changes: 70 additions & 0 deletions moodle/Tests/Sniffs/Commenting/CategorySniffTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
<?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;

// phpcs:disable moodle.NamingConventions

/**
* Test the CategorySniff sniff.
*
* @category test
* @copyright 2024 onwards Andrew Lyons <[email protected]>
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*
* @covers \MoodleHQ\MoodleCS\moodle\Sniffs\Commenting\CategorySniff
*/
class CategorySniffTest extends MoodleCSBaseTestCase
{

/**
* @dataProvider provider
*/
public function test_from_provider(
string $fixture,
array $errors,
array $warnings
): void {
$this->set_standard('moodle');
$this->set_sniff('moodle.Commenting.Category');
$this->set_fixture(sprintf("%s/fixtures/%s.php", __DIR__, $fixture));
$this->set_warnings($warnings);
$this->set_errors($errors);
$this->set_api_mapping([
'test' => [
'component' => 'core',
'allowspread' => true,
'allowlevel2' => false,
],
]);

$this->verify_cs_results();
}

public static function provider(): array {
return [
'Standard fixes' => [
'fixture' => 'category_tags',
'errors' => [
13 => 'Invalid @category tag value "core"',
],
'warnings' => [],
],
];
}
}
22 changes: 22 additions & 0 deletions moodle/Tests/Sniffs/Commenting/fixtures/category_tags.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

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

defined('MOODLE_INTERNAL') || die(); // Make this always the 1st line in all CS fixtures.

/**
* @category test
*/
class category_valid {}

/**
* @category core
*/
class category_invalid {}

/**
* Some docblock without a category.
*/
class category_missing {}

class no_docblock {}
137 changes: 137 additions & 0 deletions moodle/Tests/Util/MoodleUtilTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -470,6 +470,14 @@ protected function cleanMoodleUtilCaches() {
$moodleComponents = $moodleUtil->getProperty('moodleComponents');
$moodleComponents->setAccessible(true);
$moodleUtil->setStaticPropertyValue('moodleComponents', []);

$apiCache = $moodleUtil->getProperty('apis');
$apiCache->setAccessible(true);
$apiCache->setValue(null, []);

$apiCache = $moodleUtil->getProperty('mockedApisList');
$apiCache->setAccessible(true);
$apiCache->setValue(null, []);
}

/**
Expand Down Expand Up @@ -774,4 +782,133 @@ public function testGetTokensOnLine(): void
$this->assertCount(count($expectedTokens), $tokens);
$this->assertEquals($expectedTokens, $tokens);
}

public function testGetMoodleApis() {
$this->cleanMoodleUtilCaches();
// Let's calculate moodleRoot.
$vfs = vfsStream::setup('root', null, []);

$apis = [
'test' => [
'component' => 'core',
'allowlevel2' => false,
'allowspread' => false,
],
'time' => [
'component' => 'core',
'allowlevel2' => false,
'allowspread' => false,
],
];

vfsStream::copyFromFileSystem(__DIR__ . '/fixtures/moodleutil/complete', $vfs);
vfsStream::create(
[
'lib' => [
'apis.json' => json_encode(
$apis,
JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES
),
'example.php' => ''
],
],
$vfs
);

// We are passing a real File, prepare it.
$phpcsConfig = new Config();
$phpcsRuleset = new Ruleset($phpcsConfig);
$file = new File($vfs->url() . '/lib/lib.php', $phpcsRuleset, $phpcsConfig);

$this->assertEquals(
array_keys($apis),
MoodleUtil::getMoodleApis($file)
);
}

public function testGetMoodleApisNoApis() {
$this->cleanMoodleUtilCaches();

// Let's calculate moodleRoot.
$vfs = vfsStream::setup('root', null, []);

vfsStream::copyFromFileSystem(__DIR__ . '/fixtures/moodleutil/complete', $vfs);

// We are passing a real File, prepare it.
$phpcsConfig = new Config();
$phpcsRuleset = new Ruleset($phpcsConfig);
$file = new File($vfs->url() . '/lib/lib.php', $phpcsRuleset, $phpcsConfig);

$this->assertNull(
MoodleUtil::getMoodleApis($file)
);
}

public function testGetMoodleApisInvalidJson() {
$this->cleanMoodleUtilCaches();
// Let's calculate moodleRoot.
$vfs = vfsStream::setup('root', null, []);

vfsStream::copyFromFileSystem(__DIR__ . '/fixtures/moodleutil/complete', $vfs);
vfsStream::create(
[
'lib' => [
'apis.json' => 'invalid:"json"',
'example.php' => ''
],
],
$vfs
);

// We are passing a real File, prepare it.
$phpcsConfig = new Config();
$phpcsRuleset = new Ruleset($phpcsConfig);
$file = new File($vfs->url() . '/lib/lib.php', $phpcsRuleset, $phpcsConfig);

$this->assertNull(
MoodleUtil::getMoodleApis($file)
);
}


public function testGetMoodleApisNotAMoodle() {
$this->cleanMoodleUtilCaches();
// Let's calculate moodleRoot.
$vfs = vfsStream::setup('root', null, []);

$apis = [
'test' => [
'component' => 'core',
'allowlevel2' => false,
'allowspread' => false,
],
'time' => [
'component' => 'core',
'allowlevel2' => false,
'allowspread' => false,
],
];

vfsStream::create(
[
'lib' => [
'apis.json' => json_encode(
$apis,
JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES
),
'example.php' => ''
],
],
$vfs
);

// We are passing a real File, prepare it.
$phpcsConfig = new Config();
$phpcsRuleset = new Ruleset($phpcsConfig);
$file = new File($vfs->url() . '/lib/lib.php', $phpcsRuleset, $phpcsConfig);

$this->assertNull(
MoodleUtil::getMoodleApis($file)
);
}
}
9 changes: 9 additions & 0 deletions moodle/Util/Docblocks.php
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,15 @@ public static function getDocBlock(
int $stackPtr
): ?array {
$tokens = $phpcsFile->getTokens();
$token = $tokens[$stackPtr];

// Check if the passed pointer was for a doc.
if ($token['code'] === T_DOC_COMMENT_OPEN_TAG) {
return $token;
} else if ($token['code'] === T_DOC_COMMENT_CLOSE_TAG) {
return $tokens[$token['comment_opener']];
}

$find = [
T_ABSTRACT => T_ABSTRACT,
T_FINAL => T_FINAL,
Expand Down
Loading

0 comments on commit fc9b453

Please sign in to comment.