Skip to content

Commit

Permalink
Merge pull request moodlehq#125 from andrewnicols/fileHasSniff
Browse files Browse the repository at this point in the history
Add sniffs for file docblock @copyright and @license
  • Loading branch information
stronk7 authored Mar 19, 2024
2 parents 979a494 + f136673 commit 7c207d5
Show file tree
Hide file tree
Showing 15 changed files with 597 additions and 0 deletions.
160 changes: 160 additions & 0 deletions moodle/Sniffs/Commenting/FileExpectedTagsSniff.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
<?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\Sniffs\Commenting;

use MoodleHQ\MoodleCS\moodle\Util\Docblocks;
use MoodleHQ\MoodleCS\moodle\Util\TokenUtil;
use PHP_CodeSniffer\Config;
use PHP_CodeSniffer\Sniffs\Sniff;
use PHP_CodeSniffer\Files\File;
use PHPCSUtils\Tokens\Collections;

/**
* Checks that a file has appropriate tags in either the file, or single artefact block.
*
* @copyright 2024 Andrew Lyons <[email protected]>
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class FileExpectedTagsSniff implements Sniff
{
/**
* The regular expression used to match the expected license.
*
* Note that the regular expression is applied using preg_quote to escape as required.
*
* Note that, if the regular expression is the empty string,
* then this Sniff will do nothing.
*
* Example values:
* - Empty string or null: No check is done.
* ''
* - The GNU GPL v3 or later license with either http or https license text
* '@https?://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later@':
*
* @var null|string
*/
public ?string $preferredLicenseRegex = '@https?://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later@';

/**
* 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) {
// Get the stack pointer for the file-level docblock.
$stackPtr = Docblocks::getDocBlockPointer($phpcsFile, $stackPtr);
if ($stackPtr === null) {
// There is no file-level docblock.

if (TokenUtil::countGlobalScopesInFile($phpcsFile) > 1) {
// There are more than one item in the global scope.
// Only accept the file docblock.
return;
} else {
// There is only one item in the global scope.
// We can accept the file docblock or the item docblock.
$stackPtr = $phpcsFile->findNext(Collections::closedScopes(), 0);
}
}

$this->processFileCopyright($phpcsFile, $stackPtr);
$this->processFileLicense($phpcsFile, $stackPtr);
}

/**
* Process the file docblock and check for the presence of a @copyright tag.
*
* @param File $phpcsFile The file being scanned.
* @param int $stackPtr The position in the stack.
*/
private function processFileCopyright(File $phpcsFile, $stackPtr): void {
$copyrightTokens = Docblocks::getMatchingDocTags($phpcsFile, $stackPtr, '@copyright');
if (empty($copyrightTokens)) {
$docPtr = Docblocks::getDocBlockPointer($phpcsFile, $stackPtr);
if (empty($docPtr)) {
$docPtr = $stackPtr;
}

$phpcsFile->addError(
'Missing @copyright tag',
$docPtr,
'CopyrightTagMissing'
);
return;
}
}

/**
* Process the file docblock and check for the presence of a @license tag.
*
* @param File $phpcsFile The file being scanned.
* @param int $stackPtr The position in the stack.
*/
private function processFileLicense(File $phpcsFile, $stackPtr): void {
$tokens = $phpcsFile->getTokens();
$foundTokens = Docblocks::getMatchingDocTags($phpcsFile, $stackPtr, '@license');
if (empty($foundTokens)) {
$docPtr = Docblocks::getDocBlockPointer($phpcsFile, $stackPtr);
if ($docPtr) {
$phpcsFile->addError(
'Missing @license tag',
$docPtr,
'LicenseTagMissing'
);
} else {
$phpcsFile->addError(
'Missing @license tag',
$stackPtr,
'LicenseTagMissing'
);
}
return;
}

// If specified, get the regular expression from the config.
if (($regex = Config::getConfigData('moodleLicenseRegex')) !== null) {
$this->preferredLicenseRegex = $regex;
}

if ($this->preferredLicenseRegex === '') {
return;
}

$licensePtr = $phpcsFile->findNext(T_DOC_COMMENT_STRING, $foundTokens[0]);
$license = $tokens[$licensePtr]['content'];

if (!preg_match($this->preferredLicenseRegex, $license)) {
$phpcsFile->addWarning(
'Invalid @license tag. Value "%s" does not match expected format',
$licensePtr,
'LicenseTagInvalid',
[$license]
);
}
}
}
162 changes: 162 additions & 0 deletions moodle/Tests/Sniffs/Commenting/FileExpectedTagsSniffTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
<?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 VariableCommentSniff sniff.
*
* @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\FileExpectedTagsSniff
*/
class FileExpectedTagsSniffTest extends MoodleCSBaseTestCase
{
/**
* @dataProvider fixtureProvider
*/
public function testSniffWithFixtures(
string $fixture,
array $errors,
array $warnings,
array $configValues
): void {
$this->setStandard('moodle');
$this->setSniff('moodle.Commenting.FileExpectedTags');
$this->setFixture(sprintf("%s/fixtures/FileExpectedTags/%s.php", __DIR__, $fixture));
$this->setErrors($errors);
$this->setWarnings($warnings);

foreach ($configValues as $configKey => $configValue) {
$this->addCustomConfig($configKey, $configValue);
}

$this->verifyCsResults();
}

public static function fixtureProvider(): array {
$cases = [
'Single artifact, Single docblock' => [
'fixture' => 'single_artifact_single_docblock',
'errors' => [],
'warnings' => [],
'configValues' => [],
],
'Single artifact, Single docblock (missing)' => [
'fixture' => 'single_artifact_single_docblock_missing',
'errors' => [
3 => [
'Missing @copyright tag',
'Missing @license tag',
],
],
'warnings' => [],
'configValues' => [],
],
'Single artifact, Single docblock (missing tags)' => [
'fixture' => 'single_artifact_single_docblock_missing_tags',
'errors' => [
3 => [
'Missing @copyright tag',
'Missing @license tag',
],
],
'warnings' => [],
'configValues' => [],
],
'Single artifact, multiple docblocks' => [
'fixture' => 'single_artifact_multiple_docblock',
'errors' => [
],
'warnings' => [],
'configValues' => [],
],
'Single artifact, multiple docblocks (missing)' => [
'fixture' => 'single_artifact_multiple_docblock_missing',
'errors' => [
// Note: Covered by MissingDocblockSniff.
],
'warnings' => [],
'configValues' => [],
],
'Single artifact, multiple docblocks (wrong)' => [
'fixture' => 'single_artifact_multiple_docblock_wrong',
'errors' => [],
'warnings' => [],
'configValues' => [],
],
'Multiple artifacts, File docblock' => [
'fixture' => 'multiple_artifact_has_file_docblock',
'errors' => [],
'warnings' => [],
'configValues' => [],
],
'Multiple artifacts, File docblock (missing)' => [
'fixture' => 'multiple_artifact_has_file_docblock_missing',
'errors' => [],
'warnings' => [],
'configValues' => [],
],
'Multiple artifacts, File docblock (wrong data)' => [
'fixture' => 'multiple_artifact_has_file_docblock_wrong',
'errors' => [
3 => 'Missing @copyright tag',
],
'warnings' => [
6 => 'Invalid @license tag. Value "Me!" does not match expected format',
],
'configValues' => [],
],
'Multiple artifacts, File docblock, Custom license value set' => [
'fixture' => 'multiple_artifact_has_file_docblock_wrong',
'errors' => [
3 => 'Missing @copyright tag',
],
'warnings' => [],
'configValues' => [
'moodleLicenseRegex' => '@Me!@',
],
],
'Multiple artifacts, File docblock, No license value set' => [
'fixture' => 'multiple_artifact_has_file_docblock_wrong',
'errors' => [
3 => 'Missing @copyright tag',
],
'warnings' => [],
'configValues' => [
'moodleLicenseRegex' => '',
],
],
'Multiple artifacts, File docblock (missing tags)' => [
'fixture' => 'multiple_artifact_has_file_docblock_missing_tags',
'errors' => [
3 => [
'Missing @copyright tag',
'Missing @license tag',
],
],
'warnings' => [],
'configValues' => [],
],
];

return $cases;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

/**
* File docblock
*
* @copyright 2024 Andrew Lyons <[email protected]>
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/

/**
* Class docblock.
*
*/
class multiple_artifact_has_file_docblock
{
/**
* @var string
*/
private string $name;
}

trait example_trait {}
interface example_interface {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

/**
* Class docblock.
*
*/
class multiple_artifact_has_file_docblock_missing
{
/**
* @var string
*/
private string $name;
}

trait example_trait {}
interface example_interface {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

/**
* File docblock
*/

/**
* Class docblock.
*
*/
class multiple_artifact_has_file_docblock_missing_tags
{
/**
* @var string
*/
private string $name;
}

trait example_trait {}
interface example_interface {}
Loading

0 comments on commit 7c207d5

Please sign in to comment.