Skip to content

Commit f22c065

Browse files
kamil-zacekkukulich
authored andcommitted
Introduce rule RequireOneDocCommentSniff
1 parent 6eff0ed commit f22c065

File tree

5 files changed

+193
-0
lines changed

5 files changed

+193
-0
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ Slevomat Coding Standard for [PHP_CodeSniffer](https://github.com/PHPCSStandards
8282
- [SlevomatCodingStandard.Commenting.ForbiddenAnnotations](doc/commenting.md#slevomatcodingstandardcommentingforbiddenannotations-) 🔧
8383
- [SlevomatCodingStandard.Commenting.ForbiddenComments](doc/commenting.md#slevomatcodingstandardcommentingforbiddencomments-) 🔧
8484
- [SlevomatCodingStandard.Commenting.InlineDocCommentDeclaration](doc/commenting.md#slevomatcodingstandardcommentinginlinedoccommentdeclaration-) 🔧
85+
- [SlevomatCodingStandard.Commenting.RequireOneDocComment](doc/commenting.md#slevomatcodingstandardcommentingrequireonedoccomment-) 🔧
8586
- [SlevomatCodingStandard.Commenting.RequireOneLineDocComment](doc/commenting.md#slevomatcodingstandardcommentingrequireonelinedoccomment-) 🔧
8687
- [SlevomatCodingStandard.Commenting.RequireOneLinePropertyDocComment](doc/commenting.md#slevomatcodingstandardcommentingrequireonelinepropertydoccomment-) 🔧
8788
- [SlevomatCodingStandard.Commenting.UselessFunctionDocComment](doc/commenting.md#slevomatcodingstandardcommentinguselessfunctiondoccomment-) 🔧
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace SlevomatCodingStandard\Sniffs\Commenting;
4+
5+
use PHP_CodeSniffer\Files\File;
6+
use PHP_CodeSniffer\Sniffs\Sniff;
7+
use function implode;
8+
use function preg_match;
9+
use function preg_replace;
10+
use function preg_split;
11+
use function strpos;
12+
use function trim;
13+
use const T_COMMENT;
14+
use const T_DOC_COMMENT_OPEN_TAG;
15+
use const T_WHITESPACE;
16+
17+
class RequireOneDocCommentSniff implements Sniff
18+
{
19+
20+
public const CODE_MULTIPLE_DOC_COMMENTS = 'MultipleDocComments';
21+
22+
/**
23+
* @return array<int, (int|string)>
24+
*/
25+
public function register(): array
26+
{
27+
return [T_DOC_COMMENT_OPEN_TAG];
28+
}
29+
30+
public function process(File $phpcsFile, int $stackPointer): void
31+
{
32+
$tokens = $phpcsFile->getTokens();
33+
34+
$firstOpener = $stackPointer;
35+
$firstCloser = $tokens[$firstOpener]['comment_closer'];
36+
37+
/** @var int $next */
38+
$next = $phpcsFile->findNext([T_WHITESPACE], $firstCloser + 1, null, true);
39+
40+
if ($tokens[$next]['code'] === T_COMMENT) {
41+
return;
42+
}
43+
44+
if ($tokens[$next]['code'] !== T_DOC_COMMENT_OPEN_TAG) {
45+
return;
46+
}
47+
48+
$secondOpener = $next;
49+
$secondCloser = $tokens[$secondOpener]['comment_closer'];
50+
51+
if ($this->isSingleLineVarDoc($phpcsFile, $firstOpener, $firstCloser)
52+
&& $this->isSingleLineVarDoc($phpcsFile, $secondOpener, $secondCloser)) {
53+
return;
54+
}
55+
56+
$error = 'Only one PHPDoc comment is allowed for a documentable entity; found two adjacent doc comments.';
57+
$phpcsFile->addError($error, $firstOpener, self::CODE_MULTIPLE_DOC_COMMENTS);
58+
}
59+
60+
private function isSingleLineVarDoc(File $phpcsFile, int $opener, int $closer): bool
61+
{
62+
$length = $closer - $opener + 1;
63+
$text = $phpcsFile->getTokensAsString($opener, $length);
64+
65+
if ((strpos($text, "\n") !== false) || (strpos($text, "\r") !== false)) {
66+
return false;
67+
}
68+
69+
$inner = $this->getDocInner($text);
70+
71+
return preg_match('/^@var\b/i', $inner) === 1 && preg_match('/\$\w+\s*$/', $inner) === 1;
72+
}
73+
74+
private function getDocInner(string $doc): string
75+
{
76+
$doc = preg_replace('#\A\s*/\*\*\s*#s', '', $doc);
77+
$doc = preg_replace('#\s*\*/\s*\z#s', '', $doc);
78+
79+
/** @var list<string> $lines */
80+
$lines = preg_split("/\r\n|\n|\r/", $doc);
81+
82+
$clean = [];
83+
84+
foreach ($lines as $line) {
85+
$line = preg_replace('#^\s*\*\s?#', '', $line);
86+
$clean[] = $line;
87+
}
88+
89+
$inner = implode("\n", $clean);
90+
91+
return trim($inner, "\n\r ");
92+
}
93+
94+
}

doc/commenting.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,10 @@ Sniff provides the following settings:
9090
* `allowDocCommentAboveReturn`: Allows documentation comments without variable name above `return` statement.
9191
* `allowAboveNonAssignment`: Allows documentation comments above non-assignment if the line contains the right variable name.
9292

93+
#### SlevomatCodingStandard.Commenting.RequireOneDocComment
94+
95+
Ensures that there is only one PHPDoc comment block for each entity (class, method, property, constant, etc.). This sniff prevents multiple documentation comments from being associated with a single code element, which can lead to confusion and inconsistency.
96+
9397
#### SlevomatCodingStandard.Commenting.RequireOneLinePropertyDocComment 🔧
9498

9599
Requires property comments with single-line content to be written as one-liners.
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace SlevomatCodingStandard\Sniffs\Commenting;
4+
5+
use SlevomatCodingStandard\Sniffs\TestCase;
6+
7+
class RequireOneDocCommentSniffTest extends TestCase
8+
{
9+
10+
public function testInvalidInlineDocCommentDeclarations(): void
11+
{
12+
$report = self::checkFile(__DIR__ . '/data/requireOneDocCommentSniff.php');
13+
14+
self::assertSame(4, $report->getErrorCount());
15+
16+
self::assertSniffError($report, 6, RequireOneDocCommentSniff::CODE_MULTIPLE_DOC_COMMENTS);
17+
self::assertSniffError($report, 15, RequireOneDocCommentSniff::CODE_MULTIPLE_DOC_COMMENTS);
18+
self::assertSniffError($report, 24, RequireOneDocCommentSniff::CODE_MULTIPLE_DOC_COMMENTS);
19+
self::assertSniffError($report, 49, RequireOneDocCommentSniff::CODE_MULTIPLE_DOC_COMMENTS);
20+
}
21+
22+
public function testNoErrorsWithDocCommentAboveReturnAllowed(): void
23+
{
24+
$report = self::checkFile(__DIR__ . '/data/oneLinePropertyDocCommentErrors.fixed.php', [
25+
'allowDocCommentAboveReturn' => true,
26+
]);
27+
self::assertNoSniffErrorInFile($report);
28+
}
29+
30+
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
<?php
2+
3+
use Slevomat\Foo;
4+
use Slevomat\FooTask;
5+
6+
/**
7+
* Description
8+
*/
9+
/**
10+
* Description 2
11+
*/
12+
class Whatever
13+
{
14+
15+
/**
16+
* Description
17+
* @var string
18+
*/
19+
/**
20+
* @var int
21+
*/
22+
private $property;
23+
24+
/** @phpstan-assert-if-true !null $this->getApprovedTime() */
25+
/** @phpstan-assert-if-true null $this->getRejectedTime() */
26+
public function method()
27+
{
28+
/** @var int $productId */
29+
/** @var Foo $issue */
30+
return [1, 2, 3];
31+
}
32+
33+
/**
34+
* @param int $a
35+
* @return void
36+
*/
37+
public function bar(int $a)
38+
{
39+
/** @var int */
40+
return 5;
41+
}
42+
43+
/**
44+
* @param int $a
45+
* @return void
46+
*/
47+
public function bar2(int $a)
48+
{
49+
/** @var int */
50+
/** @var int */
51+
return 5;
52+
}
53+
54+
/**
55+
* @param FooTask $backgroundExportTask
56+
* @return bool
57+
*/
58+
// public function isAllowed(FooTask $task): bool;
59+
60+
/**
61+
* @param FooTask $backgroundExportTask
62+
*/
63+
// public function execute(FooTask $task): BackgroundExportFile;
64+
}

0 commit comments

Comments
 (0)