Skip to content

Commit 119089f

Browse files
committed
Introduce rule RequireOneDocCommentSniff
1 parent 6eff0ed commit 119089f

File tree

3 files changed

+188
-0
lines changed

3 files changed

+188
-0
lines changed
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+
}
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)