Skip to content

Commit 26a03ce

Browse files
committed
Introduce rule RequireOneDocCommentSniff
1 parent ba476e9 commit 26a03ce

File tree

3 files changed

+200
-0
lines changed

3 files changed

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