Skip to content

Commit

Permalink
Create FileHas Sniff to check for copyright + license
Browse files Browse the repository at this point in the history
  • Loading branch information
andrewnicols committed Mar 19, 2024
1 parent f8c2af7 commit 10c69f1
Show file tree
Hide file tree
Showing 16 changed files with 479 additions and 22 deletions.
102 changes: 80 additions & 22 deletions moodle/Sniffs/Commenting/FileHasSniff.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,11 @@

use MoodleHQ\MoodleCS\moodle\Util\Tokens;
use MoodleHQ\MoodleCS\moodle\Util\Docblocks;
use MoodleHQ\MoodleCS\moodle\Util\TokenUtil;
use PHP_CodeSniffer\Sniffs\Sniff;
use PHP_CodeSniffer\Files\File;
use PHPCSUtils\Tokens\Collections;
use phpDocumentor\Reflection\Types\Collection;

/**
* Checks that a file has appropriate tags in either the file, or single artefact block.
Expand All @@ -30,12 +33,15 @@
*/
class FileHasSniff implements Sniff
{
/** @var string The license text expected */
protected static $licenseText = '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_DOC_COMMENT_OPEN_TAG,
T_OPEN_TAG,
];
}

Expand All @@ -46,45 +52,97 @@ public function register() {
* @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)) {
$phpcsFile->addError(
'Missing @copyright tag',
$stackPtr,
'Missing'
'CopyrightTagMissing'
);
return;
}
}

$docblockPtr = Docblocks::getDocBlockPointer($phpcsFile, $stackPtr);
$objectType = Tokens::getObjectType($phpcsFile, $docblockPtr);
$find = [
T_CLASS,
T_FUNCTION,
T_ENUM,
T_INTERFACE,
T_TRAIT,
T_OPEN_TAG,
];
$owner = $phpcsFile->findPrevious($find, $docblockPtr);
var_dump($docblockPtr);
var_dump($objectType);
if ($docblockPtr && $objectType === 'file') {
$copyrightTokens = Docblocks::getMatchingDocTags($phpcsFile, $stackPtr, '@copyright');
if (count($copyrightTokens) === 0) {
/**
* 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) {
$fix = $phpcsFile->addFixableError(
'Missing @license tag',
$stackPtr,
'LicenseTagMissing'
);
if ($fix) {
$licenseText = self::$licenseText;
$endPtr = $tokens[$docPtr]['comment_closer'];

$phpcsFile->fixer->beginChangeset();
$phpcsFile->fixer->addNewlineBefore($endPtr - 1);
$phpcsFile->fixer->addContentBefore($endPtr - 1, " * @license {$licenseText}");
$phpcsFile->fixer->endChangeset();
}
} else {
$phpcsFile->addError(
'Missing @copyright tag in file docblock',
'Missing @license tag',
$stackPtr,
'Missing'
'LicenseTagMissing'
);
}

// This file has a file docblock. Copyright should be here regardless.
return;
}

$tokens = $phpcsFile->getTokens();

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

if ($license !== self::$licenseText) {
$fix = $phpcsFile->addFixableWarning(
'Invalid @license tag. Expected "%s", found "%s"',
$licensePtr,
'LicenseTagInvalid',
[self::$licenseText, $license]
);
if ($fix) {
$phpcsFile->fixer->beginChangeset();
$phpcsFile->fixer->replaceToken($licensePtr, self::$licenseText);
$phpcsFile->fixer->endChangeset();
}
}
}
}
162 changes: 162 additions & 0 deletions moodle/Tests/Sniffs/Commenting/FileHasSniffTest.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\FileHasSniff
*/
class FileHasSniffTest extends MoodleCSBaseTestCase
{
/**
* @dataProvider fixtureProvider
*/
public function testSniffWithFixtures(
string $fixture,
array $errors,
array $warnings
): void {
$this->setStandard('moodle');
$this->setSniff('moodle.Commenting.FileHas');
$this->setFixture(sprintf("%s/fixtures/FileHas/%s.php", __DIR__, $fixture));
$this->setErrors($errors);
$this->setWarnings($warnings);

$this->verifyCsResults();
}

public static function fixtureProvider(): array {
$cases = [
'Single artifact, Single docblock' => [
'fixture' => 'single_artifact_single_docblock',
'errors' => [],
'warnings' => [],
],
'Single artifact, Single docblock (missing)' => [
'fixture' => 'single_artifact_single_docblock_missing',
'errors' => [
3 => [
'Missing @copyright tag',
'Missing @license tag',
],
],
'warnings' => [],
],
'Single artifact, Single docblock (missing tags)' => [
'fixture' => 'single_artifact_single_docblock_missing_tags',
'errors' => [
6 => [
'Missing @copyright tag',
'Missing @license tag',
],
],
'warnings' => [],
],
'Single artifact, multiple docblocks' => [
'fixture' => 'single_artifact_multiple_docblock',
'errors' => [
],
'warnings' => [],
],
'Single artifact, multiple docblocks (missing)' => [
'fixture' => 'single_artifact_multiple_docblock_missing',
'errors' => [
// Note: Covered by MissingDocblockSniff.
],
'warnings' => [],
],
'Single artifact, multiple docblocks (wrong)' => [
'fixture' => 'single_artifact_multiple_docblock_wrong',
'errors' => [
],
'warnings' => [
7 => 'Invalid @license tag. '
. 'Expected "https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later", '
. 'found "http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later"',
],
],
'Multiple artifacts, File docblock' => [
'fixture' => 'multiple_artifact_has_file_docblock',
'errors' => [],
'warnings' => [],
],
'Multiple artifacts, File docblock (missing)' => [
'fixture' => 'multiple_artifact_has_file_docblock_missing',
'errors' => [],
'warnings' => [],
],
'Multiple artifacts, File docblock (wrong data)' => [
'fixture' => 'multiple_artifact_has_file_docblock_wrong',
'errors' => [
3 => 'Missing @copyright tag',
],
'warnings' => [
6 => 'Invalid @license tag. Expected "https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later", found "Me!"',
],
],
'Multiple artifacts, File docblock (missing tags)' => [
'fixture' => 'multiple_artifact_has_file_docblock_missing_tags',
'errors' => [
3 => [
'Missing @copyright tag',
'Missing @license tag',
],
],
'warnings' => [],
],
];

// if (version_compare(PHP_VERSION, '8.0.0') >= 0) {
// // Since PHP 8.0.
// $cases['Constructor Property Promotions'] = [
// 'fixture' => 'constructor_property_promotion',
// 'errors' => [
// 7 => 'Missing member variable doc comment',
// 13 => 'Expected "bool" but found "BOOLEAN" for @var tag in member variable comment',
// 20 => 'The @var tag must be the first tag in a member variable commen',
// ],
// 'warnings' => [
// 19 => '@deprecated tag is not allowed in member variable comment',
// ],
// ];
// }

// if (version_compare(PHP_VERSION, '8.1.0') >= 0) {
// // Since PHP 8.1.
// $cases['Constructor Property Promotions with Readonly params'] = [
// 'fixture' => 'constructor_property_promotion_readonly',
// 'errors' => [
// 7 => 'Missing member variable doc comment',
// 13 => 'Expected "bool" but found "BOOLEAN" for @var tag in member variable comment',
// 20 => 'The @var tag must be the first tag in a member variable commen',
// ],
// 'warnings' => [
// 19 => '@deprecated tag is not allowed in member variable comment',
// ],
// ];
// }

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 {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

/**
* File docblock
*
* @license Me!
*/

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

trait example_trait {}
interface example_interface {}
Loading

0 comments on commit 10c69f1

Please sign in to comment.