Skip to content

Commit 254d707

Browse files
authored
Merge pull request #110 from andrewnicols/correctPackageTag
Check package tags
2 parents 61a380a + dbbffda commit 254d707

33 files changed

+1187
-3
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ The format of this change log follows the advice given at [Keep a CHANGELOG](htt
88
## [Unreleased]
99
### Changed
1010
- Update composer dependencies to current versions, notably `PHP_CodeSniffer` (3.9.0) and `PHPCompatibility` (e5cd2e24).
11+
- Add new moodle.Commenting.Package sniffs to replace those present in moodle-local_moodlecheck.
1112

1213
## [v3.3.15] - 2024-02-15
1314
### Added
Lines changed: 233 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,233 @@
1+
<?php
2+
// This file is part of Moodle - http://moodle.org/
3+
//
4+
// Moodle is free software: you can redistribute it and/or modify
5+
// it under the terms of the GNU General Public License as published by
6+
// the Free Software Foundation, either version 3 of the License, or
7+
// (at your option) any later version.
8+
//
9+
// Moodle is distributed in the hope that it will be useful,
10+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
// GNU General Public License for more details.
13+
//
14+
// You should have received a copy of the GNU General Public License
15+
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
16+
17+
namespace MoodleHQ\MoodleCS\moodle\Sniffs\Commenting;
18+
19+
// phpcs:disable moodle.NamingConventions
20+
21+
use MoodleHQ\MoodleCS\moodle\Util\MoodleUtil;
22+
use MoodleHQ\MoodleCS\moodle\Util\Docblocks;
23+
use PHP_CodeSniffer\Sniffs\Sniff;
24+
use PHP_CodeSniffer\Files\File;
25+
use PHPCSUtils\Utils\ObjectDeclarations;
26+
27+
/**
28+
* Checks that all test classes and global functions have appropriate @package tags.
29+
*
30+
* @copyright 2024 Andrew Lyons <[email protected]>
31+
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
32+
*/
33+
class PackageSniff implements Sniff {
34+
35+
/**
36+
* Register for open tag (only process once per file).
37+
*/
38+
public function register() {
39+
return [
40+
T_OPEN_TAG,
41+
];
42+
}
43+
44+
/**
45+
* Processes php files and perform various checks with file.
46+
*
47+
* @param File $phpcsFile The file being scanned.
48+
* @param int $stackPtr The position in the stack.
49+
*/
50+
public function process(File $phpcsFile, $stackPtr) {
51+
$tokens = $phpcsFile->getTokens();
52+
53+
$docblock = Docblocks::getDocBlock($phpcsFile, $stackPtr);
54+
if ($docblock) {
55+
$filePackageFound = $this->checkDocblock(
56+
$phpcsFile,
57+
$stackPtr,
58+
$docblock
59+
);
60+
if ($filePackageFound) {
61+
return;
62+
}
63+
}
64+
65+
$find = [
66+
T_CLASS,
67+
T_FUNCTION,
68+
T_TRAIT,
69+
T_INTERFACE,
70+
];
71+
$typePtr = $stackPtr + 1;
72+
while ($typePtr = $phpcsFile->findNext($find, $typePtr + 1)) {
73+
$token = $tokens[$typePtr];
74+
if ($token['code'] === T_FUNCTION && !empty($token['conditions'])) {
75+
// Skip methods of classes, traits and interfaces.
76+
continue;
77+
}
78+
79+
$docblock = Docblocks::getDocBlock($phpcsFile, $typePtr);
80+
81+
if ($docblock === null) {
82+
$objectName = $this->getObjectName($phpcsFile, $typePtr);
83+
$objectType = $this->getObjectType($phpcsFile, $typePtr);
84+
$phpcsFile->addError('Missing doc comment for %s %s', $typePtr, 'Missing', [$objectType, $objectName]);
85+
86+
continue;
87+
}
88+
89+
$this->checkDocblock($phpcsFile, $typePtr, $docblock);
90+
}
91+
92+
}
93+
94+
/**
95+
* Get the human-readable object type.
96+
*
97+
* @param File $phpcsFile
98+
* @param int $stackPtr
99+
* @return string
100+
*/
101+
protected function getObjectType(
102+
File $phpcsFile,
103+
int $stackPtr
104+
): string {
105+
$tokens = $phpcsFile->getTokens();
106+
if ($tokens[$stackPtr]['code'] === T_OPEN_TAG) {
107+
return 'file';
108+
}
109+
return $tokens[$stackPtr]['content'];
110+
}
111+
112+
/**
113+
* Get the human readable object name.
114+
*
115+
* @param File $phpcsFile
116+
* @param int $stackPtr
117+
* @return string
118+
*/
119+
protected function getObjectName(
120+
File $phpcsFile,
121+
int $stackPtr
122+
): string {
123+
$tokens = $phpcsFile->getTokens();
124+
if ($tokens[$stackPtr]['code'] === T_OPEN_TAG) {
125+
return basename($phpcsFile->getFilename());
126+
}
127+
128+
return ObjectDeclarations::getName($phpcsFile, $stackPtr);
129+
}
130+
131+
/**
132+
* Check the docblock for a @package tag.
133+
*
134+
* @param File $phpcsFile
135+
* @param int $stackPtr
136+
* @param array $docblock
137+
* @return bool Whether any package tag was found, whether or not it was correct
138+
*/
139+
protected function checkDocblock(
140+
File $phpcsFile,
141+
int $stackPtr,
142+
array $docblock
143+
): bool {
144+
$tokens = $phpcsFile->getTokens();
145+
$objectName = $this->getObjectName($phpcsFile, $stackPtr);
146+
$objectType = $this->getObjectType($phpcsFile, $stackPtr);
147+
$expectedPackage = MoodleUtil::getMoodleComponent($phpcsFile, true);
148+
149+
$packageTokens = Docblocks::getMatchingDocTags($phpcsFile, $stackPtr, '@package');
150+
if (empty($packageTokens)) {
151+
$fix = $phpcsFile->addFixableError(
152+
'DocBlock missing a @package tag for %s %s. Expected @package %s',
153+
$stackPtr,
154+
'Missing',
155+
[$objectType, $objectName, $expectedPackage]
156+
);
157+
158+
if ($fix) {
159+
$phpcsFile->fixer->beginChangeset();
160+
$phpcsFile->fixer->addContentBefore($docblock['comment_closer'], '* @package ' . $expectedPackage . PHP_EOL . ' ');
161+
$phpcsFile->fixer->endChangeset();
162+
}
163+
164+
return false;
165+
}
166+
167+
if (count($packageTokens) > 1) {
168+
$fix = $phpcsFile->addFixableError(
169+
'More than one @package tag found in %s %s.',
170+
$stackPtr,
171+
'Multiple',
172+
[$objectType, $objectName]
173+
);
174+
175+
if ($fix) {
176+
$phpcsFile->fixer->beginChangeset();
177+
$validTokenFound = false;
178+
179+
foreach ($packageTokens as $i => $packageToken) {
180+
$packageValuePtr = $phpcsFile->findNext(
181+
T_DOC_COMMENT_STRING,
182+
$packageToken,
183+
$docblock['comment_closer']
184+
);
185+
$packageValue = $tokens[$packageValuePtr]['content'];
186+
if (!$validTokenFound && $packageValue === $expectedPackage) {
187+
$validTokenFound = true;
188+
continue;
189+
}
190+
$lineNo = $tokens[$packageToken]['line'];
191+
foreach (array_keys(MoodleUtil::getTokensOnLine($phpcsFile, $lineNo)) as $lineToken) {
192+
$phpcsFile->fixer->replaceToken($lineToken, '');
193+
}
194+
}
195+
if (!$validTokenFound) {
196+
$phpcsFile->fixer->addContentBefore($packageTokens[0], ' * @package ' . $expectedPackage . PHP_EOL);
197+
}
198+
$phpcsFile->fixer->endChangeset();
199+
}
200+
return true;
201+
}
202+
203+
$packageToken = reset($packageTokens);
204+
205+
// Check the value of the package tag.
206+
$packageValuePtr = $phpcsFile->findNext(
207+
T_DOC_COMMENT_STRING,
208+
$packageToken,
209+
$docblock['comment_closer']
210+
);
211+
$packageValue = $tokens[$packageValuePtr]['content'];
212+
213+
// Compare to expected value.
214+
if ($packageValue === $expectedPackage) {
215+
return true;
216+
}
217+
218+
$fix = $phpcsFile->addFixableError(
219+
'Incorrect @package tag for %s %s. Expected %s, found %s.',
220+
$packageToken,
221+
'Incorrect',
222+
[$objectType, $objectName, $expectedPackage, $packageValue]
223+
);
224+
225+
if ($fix) {
226+
$phpcsFile->fixer->beginChangeset();
227+
$phpcsFile->fixer->replaceToken($packageValuePtr, $expectedPackage);
228+
$phpcsFile->fixer->endChangeset();
229+
}
230+
231+
return true;
232+
}
233+
}
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
<?php
2+
// This file is part of Moodle - https://moodle.org/
3+
//
4+
// Moodle is free software: you can redistribute it and/or modify
5+
// it under the terms of the GNU General Public License as published by
6+
// the Free Software Foundation, either version 3 of the License, or
7+
// (at your option) any later version.
8+
//
9+
// Moodle is distributed in the hope that it will be useful,
10+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
// GNU General Public License for more details.
13+
//
14+
// You should have received a copy of the GNU General Public License
15+
// along with Moodle. If not, see <https://www.gnu.org/licenses/>.
16+
17+
namespace MoodleHQ\MoodleCS\moodle\Tests\Sniffs\Commenting;
18+
19+
use MoodleHQ\MoodleCS\moodle\Tests\MoodleCSBaseTestCase;
20+
21+
// phpcs:disable moodle.NamingConventions
22+
23+
/**
24+
* Test the TestCaseNamesSniff sniff.
25+
*
26+
* @category test
27+
* @copyright 2024 onwards Andrew Lyons <[email protected]>
28+
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
29+
*
30+
* @covers \MoodleHQ\MoodleCS\moodle\Sniffs\Commenting\PackageSniff
31+
*/
32+
class PackageSniffTest extends MoodleCSBaseTestCase
33+
{
34+
35+
/**
36+
* @dataProvider package_correctness_provider
37+
*/
38+
public function test_package_correctness(
39+
string $fixture,
40+
array $errors,
41+
array $warnings
42+
): void {
43+
$this->set_standard('moodle');
44+
$this->set_sniff('moodle.Commenting.Package');
45+
$this->set_fixture(sprintf("%s/fixtures/%s.php", __DIR__, $fixture));
46+
$this->set_warnings($warnings);
47+
$this->set_errors($errors);
48+
$this->set_component_mapping([
49+
'local_codechecker' => dirname(__DIR__),
50+
]);
51+
52+
$this->verify_cs_results();
53+
}
54+
55+
public static function package_correctness_provider(): array {
56+
return [
57+
'Standard fixes' => [
58+
'fixture' => 'package_tags',
59+
'errors' => [
60+
18 => 'DocBlock missing a @package tag for function package_missing. Expected @package local_codechecker',
61+
31 => 'DocBlock missing a @package tag for class package_absent. Expected @package local_codechecker',
62+
34 => 'Missing doc comment for function missing_docblock_in_function',
63+
38 => 'Missing doc comment for class missing_docblock_in_class',
64+
42 => 'Incorrect @package tag for function package_wrong_in_function. Expected local_codechecker, found wrong_package.',
65+
48 => 'Incorrect @package tag for class package_wrong_in_class. Expected local_codechecker, found wrong_package.',
66+
57 => 'More than one @package tag found in function package_multiple_in_function',
67+
64 => 'More than one @package tag found in class package_multiple_in_class',
68+
71 => 'More than one @package tag found in function package_multiple_in_function_all_wrong',
69+
78 => 'More than one @package tag found in class package_multiple_in_class_all_wrong',
70+
85 => 'More than one @package tag found in interface package_multiple_in_interface_all_wrong',
71+
92 => 'More than one @package tag found in trait package_multiple_in_trait_all_wrong',
72+
95 => 'Missing doc comment for interface missing_docblock_interface',
73+
101 => 'DocBlock missing a @package tag for interface missing_package_interface. Expected @package local_codechecker',
74+
106 => 'Incorrect @package tag for interface incorrect_package_interface. Expected local_codechecker, found local_codecheckers.',
75+
118 => 'Missing doc comment for trait missing_docblock_trait',
76+
124 => 'DocBlock missing a @package tag for trait missing_package_trait. Expected @package local_codechecker',
77+
129 => 'Incorrect @package tag for trait incorrect_package_trait. Expected local_codechecker, found local_codecheckers.',
78+
],
79+
'warnings' => [],
80+
],
81+
'File level tag (wrong)' => [
82+
'fixture' => 'package_tags_file_wrong',
83+
'errors' => [
84+
20 => 'Incorrect @package tag for file package_tags_file_wrong.php. Expected local_codechecker, found core.',
85+
],
86+
'warnings' => [],
87+
],
88+
'File level tag (right)' => [
89+
'fixture' => 'package_tags_file_right',
90+
'errors' => [],
91+
'warnings' => [],
92+
],
93+
];
94+
}
95+
}

0 commit comments

Comments
 (0)