Skip to content

Commit a439463

Browse files
committed
Implement rule from #105
1 parent fde7fc5 commit a439463

31 files changed

+441
-102
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
<?php
2+
3+
/**
4+
* Copyright © Magento. All rights reserved.
5+
* See COPYING.txt for license details.
6+
*/
7+
namespace Magento2\Sniffs\Commenting;
8+
9+
use PHP_CodeSniffer\Sniffs\Sniff;
10+
use PHP_CodeSniffer\Files\File;
11+
12+
/**
13+
* Detects PHPDoc formatting for classes and interfaces.
14+
*/
15+
class ClassAndInterfacePHPDocFormattingSniff implements Sniff
16+
{
17+
/**
18+
* @var PHPDocFormattingValidator
19+
*/
20+
private $PHPDocFormattingValidator;
21+
22+
/**
23+
* @var string[] List of tags that can not be used in comments
24+
*/
25+
public $forbiddenTags = [
26+
'@category',
27+
'@package',
28+
'@subpackage'
29+
];
30+
31+
/**
32+
* Helper initialisation
33+
*/
34+
public function __construct()
35+
{
36+
$this->PHPDocFormattingValidator = new PHPDocFormattingValidator();
37+
}
38+
39+
/**
40+
* @inheritDoc
41+
*/
42+
public function register()
43+
{
44+
return [
45+
T_CLASS,
46+
T_INTERFACE
47+
];
48+
}
49+
50+
/**
51+
* @inheritDoc
52+
*/
53+
public function process(File $phpcsFile, $stackPtr)
54+
{
55+
$tokens = $phpcsFile->getTokens();
56+
57+
$namePtr = $phpcsFile->findNext(T_STRING, $stackPtr + 1, null, false, null, true);
58+
59+
$commentStartPtr = $phpcsFile->findPrevious(
60+
[
61+
T_WHITESPACE,
62+
T_DOC_COMMENT_STAR,
63+
T_DOC_COMMENT_WHITESPACE,
64+
T_DOC_COMMENT_TAG,
65+
T_DOC_COMMENT_STRING,
66+
T_DOC_COMMENT_CLOSE_TAG
67+
],
68+
$stackPtr - 1,
69+
null,
70+
true,
71+
null,
72+
true
73+
);
74+
75+
if ($tokens[$commentStartPtr]['code'] !== T_DOC_COMMENT_OPEN_TAG) {
76+
return;
77+
}
78+
79+
if ($this->PHPDocFormattingValidator->providesMeaning($namePtr, $commentStartPtr, $tokens) !== true) {
80+
$phpcsFile->addWarning(
81+
sprintf(
82+
'%s description should contain additional information beyond the name already supplies.',
83+
ucfirst($tokens[$stackPtr]['content'])
84+
),
85+
$stackPtr,
86+
'InvalidDescription'
87+
);
88+
}
89+
90+
$this->validateTags($phpcsFile, $commentStartPtr, $tokens);
91+
}
92+
93+
/**
94+
* Validates that forbidden tags are not used in comment
95+
*
96+
* @param File $phpcsFile
97+
* @param int $commentStartPtr
98+
* @param array $tokens
99+
* @return bool
100+
*/
101+
private function validateTags(File $phpcsFile, $commentStartPtr, $tokens)
102+
{
103+
$commentCloserPtr = $tokens[$commentStartPtr]['comment_closer'];
104+
105+
for ($i = $commentStartPtr; $i <= $commentCloserPtr; $i++) {
106+
if ($tokens[$i]['code'] !== T_DOC_COMMENT_TAG) {
107+
continue;
108+
}
109+
110+
if (in_array($tokens[$i]['content'], $this->forbiddenTags) === true) {
111+
$phpcsFile->addWarning(
112+
sprintf('Tag %s MUST NOT be used.', $tokens[$i]['content']),
113+
$i,
114+
'ForbiddenTags'
115+
);
116+
}
117+
}
118+
119+
return false;
120+
}
121+
}

Diff for: Magento2/Sniffs/Commenting/ConstantsPHPDocFormattingSniff.php

+19-30
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,19 @@
1313
*/
1414
class ConstantsPHPDocFormattingSniff implements Sniff
1515
{
16+
/**
17+
* @var PHPDocFormattingValidator
18+
*/
19+
private $PHPDocFormattingValidator;
20+
21+
/**
22+
* Helper initialisation
23+
*/
24+
public function __construct()
25+
{
26+
$this->PHPDocFormattingValidator = new PHPDocFormattingValidator();
27+
}
28+
1629
/**
1730
* @inheritDoc
1831
*/
@@ -45,42 +58,18 @@ public function process(File $phpcsFile, $stackPtr)
4558
null,
4659
true
4760
);
48-
$constName = strtolower(trim($tokens[$constNamePtr]['content'], " '\""));
4961

5062
$commentStartPtr = $phpcsFile->findPrevious(T_DOC_COMMENT_OPEN_TAG, $stackPtr - 1, null, false, null, true);
5163
if ($commentStartPtr === false) {
5264
return;
5365
}
5466

55-
$commentCloserPtr = $tokens[$commentStartPtr]['comment_closer'];
56-
for ($i = $commentStartPtr; $i <= $commentCloserPtr; $i++) {
57-
$token = $tokens[$i];
58-
59-
// Not an interesting string
60-
if ($token['code'] !== T_DOC_COMMENT_STRING) {
61-
continue;
62-
}
63-
64-
// Comment is the same as constant name
65-
$docComment = trim(strtolower($token['content']), ',.');
66-
if ($docComment === $constName) {
67-
continue;
68-
}
69-
70-
// Comment is exactly the same as constant name
71-
$docComment = str_replace(' ', '_', $docComment);
72-
if ($docComment === $constName) {
73-
continue;
74-
}
75-
76-
// We have found at lease one meaningful line in comment description
77-
return;
67+
if ($this->PHPDocFormattingValidator->providesMeaning($constNamePtr, $commentStartPtr, $tokens) !== true) {
68+
$phpcsFile->addWarning(
69+
'Constants must have short description if they add information beyond what the constant name supplies.',
70+
$stackPtr,
71+
'MissingConstantPHPDoc'
72+
);
7873
}
79-
80-
$phpcsFile->addWarning(
81-
'Constants must have short description if they add information beyond what the constant name supplies.',
82-
$stackPtr,
83-
'MissingConstantPHPDoc'
84-
);
8574
}
8675
}
+74
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
<?php
2+
3+
/**
4+
* Copyright © Magento. All rights reserved.
5+
* See COPYING.txt for license details.
6+
*/
7+
namespace Magento2\Sniffs\Commenting;
8+
9+
/**
10+
* Helper class for common DocBlock validations
11+
*/
12+
class PHPDocFormattingValidator
13+
{
14+
/**
15+
* Determines if the comment identified by $commentStartPtr provides additional meaning to origin at $namePtr
16+
*
17+
* @param int $namePtr
18+
* @param int $commentStartPtr
19+
* @param array $tokens
20+
* @return bool
21+
*/
22+
public function providesMeaning($namePtr, $commentStartPtr, $tokens)
23+
{
24+
$commentCloserPtr = $tokens[$commentStartPtr]['comment_closer'];
25+
$name = strtolower(str_replace([' ', '"', '_'], '', $tokens[$namePtr]['content']));
26+
27+
$hasTags = false;
28+
$hasDescription = false;
29+
30+
for ($i = $commentStartPtr; $i <= $commentCloserPtr; $i++) {
31+
$token = $tokens[$i];
32+
33+
// Important, but not the string we are looking for
34+
if ($token['code'] === T_DOC_COMMENT_TAG) {
35+
$hasTags = true;
36+
continue;
37+
}
38+
39+
// Not an interesting string
40+
if ($token['code'] !== T_DOC_COMMENT_STRING) {
41+
continue;
42+
}
43+
44+
// Wrong kind of string
45+
if ($tokens[$i - 2]['code'] === T_DOC_COMMENT_TAG) {
46+
continue;
47+
}
48+
49+
$hasDescription = true;
50+
51+
// Comment is the same as the origin name
52+
$docComment = str_replace(['_', ' ', '.', ','], '', strtolower($token['content']));
53+
if ($docComment === $name) {
54+
continue;
55+
}
56+
57+
// Only difference is word Class or Interface
58+
$docComment = str_replace(['class', 'interface'], '', $docComment);
59+
if ($docComment === $name) {
60+
continue;
61+
}
62+
63+
// We have found at lease one meaningful line in comment description
64+
return true;
65+
}
66+
67+
// Contains nothing but the tags
68+
if ($hasTags === true && $hasDescription === false) {
69+
return true;
70+
}
71+
72+
return false;
73+
}
74+
}

Diff for: Magento2/Tests/CodeAnalysis/EmptyBlockUnitTest.php

-3
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,6 @@
77

88
use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest;
99

10-
/**
11-
* Class EmptyBlockUnitTest
12-
*/
1310
class EmptyBlockUnitTest extends AbstractSniffUnitTest
1411
{
1512
/**
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
<?php
2+
3+
/**
4+
* Handler for PHP errors/warnings/notices that converts them to exceptions.
5+
*/
6+
class ErrorHandler
7+
{
8+
9+
}
10+
11+
class NotAnErrorHandler
12+
{
13+
14+
}
15+
16+
/**
17+
* Faulty Handler
18+
*/
19+
class FaultyHandler
20+
{
21+
22+
}
23+
24+
/**
25+
* Class SomeHandler
26+
*/
27+
class SomeHandler
28+
{
29+
30+
}
31+
32+
/**
33+
* YetAnotherHandler
34+
*/
35+
class YetAnotherHandler
36+
{
37+
38+
}
39+
40+
/**
41+
* GreenHandler
42+
* @api Do not confuse tag for faulty short description
43+
*/
44+
class GreenHandler
45+
{
46+
47+
}
48+
49+
/**
50+
*
51+
*/
52+
class EmptyHandler
53+
{
54+
55+
}
56+
57+
/**
58+
* Handler for PHP errors/warnings/notices that converts them to exceptions.
59+
*
60+
* @api is ok here
61+
* @deprecated can be used in this context
62+
* @author is actually ok
63+
* @category is irrelevant
64+
* @package is not ment to be used
65+
* @subpackage does not belong here
66+
*/
67+
class ExampleHandler
68+
{
69+
70+
}
71+
72+
/**
73+
* @api
74+
* @since 100.0.2
75+
*/
76+
class ApiHandler
77+
{
78+
79+
}
80+
81+
/**
82+
* @api
83+
*/
84+
class AsyncApiHandler
85+
{
86+
87+
}
88+
89+
/**
90+
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
91+
*/
92+
class GroupRepositoryHandler
93+
{}

0 commit comments

Comments
 (0)