Skip to content

Commit 8b6260c

Browse files
committed
PHPDoc tag @phpstan-ignore-next-line works for first line below the PHPDoc even in bleeding edge
1 parent c370cbf commit 8b6260c

File tree

4 files changed

+66
-10
lines changed

4 files changed

+66
-10
lines changed

src/Parser/RichParser.php

+30-5
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,17 @@
1111
use PHPStan\File\FileReader;
1212
use PHPStan\ShouldNotHappenException;
1313
use function array_filter;
14+
use function count;
15+
use function implode;
1416
use function is_string;
17+
use function preg_match_all;
18+
use function sprintf;
1519
use function str_contains;
1620
use function strpos;
1721
use function substr;
1822
use function substr_count;
1923
use const ARRAY_FILTER_USE_KEY;
24+
use const PREG_OFFSET_CAPTURE;
2025
use const T_COMMENT;
2126
use const T_DOC_COMMENT;
2227

@@ -25,6 +30,10 @@ class RichParser implements Parser
2530

2631
public const VISITOR_SERVICE_TAG = 'phpstan.parser.richParserNodeVisitor';
2732

33+
private const PHPDOC_TAG_REGEX = '(@(?:[a-z][a-z0-9-\\\\]+:)?[a-z][a-z0-9-\\\\]*+)';
34+
35+
private const PHPDOC_DOCTRINE_TAG_REGEX = '(@[a-z_\\\\][a-z0-9_\:\\\\]*[a-z_][a-z0-9_]*)';
36+
2837
public function __construct(
2938
private \PhpParser\Parser $parser,
3039
private Lexer $lexer,
@@ -107,11 +116,27 @@ private function getLinesToIgnore(array $tokens): array
107116
$text = $token[1];
108117
$line = $token[2];
109118

110-
if ($this->enableIgnoreErrorsWithinPhpDocs) {
111-
$lines = $lines +
112-
$this->getLinesToIgnoreForTokenByIgnoreComment($text, $line, '@phpstan-ignore-next-line', true) +
113-
$this->getLinesToIgnoreForTokenByIgnoreComment($text, $line, '@phpstan-ignore-line');
114-
119+
if ($this->enableIgnoreErrorsWithinPhpDocs && $type === T_DOC_COMMENT) {
120+
$lines += $this->getLinesToIgnoreForTokenByIgnoreComment($text, $line, '@phpstan-ignore-line');
121+
if (str_contains($text, '@phpstan-ignore-next-line')) {
122+
$pattern = sprintf('~%s~si', implode('|', [self::PHPDOC_TAG_REGEX, self::PHPDOC_DOCTRINE_TAG_REGEX]));
123+
$r = preg_match_all($pattern, $text, $pregMatches, PREG_OFFSET_CAPTURE);
124+
if ($r !== false) {
125+
$c = count($pregMatches[0]);
126+
if ($c > 0) {
127+
[$lastMatchTag, $lastMatchOffset] = $pregMatches[0][$c - 1];
128+
if ($lastMatchTag === '@phpstan-ignore-next-line') {
129+
// this will let us ignore errors outside of PHPDoc
130+
// and also cut off the PHPDoc text before the last tag
131+
$lineToIgnore = $line + 1 + substr_count($text, "\n");
132+
$lines[$lineToIgnore] = null;
133+
$text = substr($text, 0, $lastMatchOffset);
134+
}
135+
}
136+
}
137+
138+
$lines += $this->getLinesToIgnoreForTokenByIgnoreComment($text, $line, '@phpstan-ignore-next-line', true);
139+
}
115140
} else {
116141
if (str_contains($text, '@phpstan-ignore-next-line')) {
117142
$line++;

tests/PHPStan/Analyser/AnalyserTest.php

+5-5
Original file line numberDiff line numberDiff line change
@@ -467,11 +467,11 @@ public function testDoNotReportUnmatchedIgnoredErrorsFromPathWithCountIfPathWasN
467467

468468
public function testIgnoreNextLine(): void
469469
{
470-
$result = $this->runAnalyser([], true, [
470+
$result = $this->runAnalyser([], false, [
471471
__DIR__ . '/data/ignore-next-line.php',
472472
], true);
473-
$this->assertCount(3, $result);
474-
foreach ([10, 20, 24] as $i => $line) {
473+
$this->assertCount(5, $result);
474+
foreach ([10, 20, 24, 31, 50] as $i => $line) {
475475
$this->assertArrayHasKey($i, $result);
476476
$this->assertInstanceOf(Error::class, $result[$i]);
477477
$this->assertSame('Fail.', $result[$i]->getMessage());
@@ -484,8 +484,8 @@ public function testIgnoreNextLineUnmatched(): void
484484
$result = $this->runAnalyser([], true, [
485485
__DIR__ . '/data/ignore-next-line-unmatched.php',
486486
], true);
487-
$this->assertCount(1, $result);
488-
foreach ([11] as $i => $line) {
487+
$this->assertCount(2, $result);
488+
foreach ([11, 15] as $i => $line) {
489489
$this->assertArrayHasKey($i, $result);
490490
$this->assertInstanceOf(Error::class, $result[$i]);
491491
$this->assertStringContainsString('No error to ignore is reported on line', $result[$i]->getMessage());

tests/PHPStan/Analyser/data/ignore-next-line-unmatched.php

+6
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,12 @@ public function doFoo(): void
99
{
1010
/** @phpstan-ignore-next-line */
1111
succ(); // reported as unmatched
12+
13+
/**
14+
* @phpstan-ignore-next-line
15+
* @var int
16+
*/
17+
succ(); // not reported as unmatched because phpstan-ignore-next-line is not last
1218
}
1319

1420
}

tests/PHPStan/Analyser/data/ignore-next-line.php

+25
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,31 @@ public function doFoo(): void
2323
if (fail()) {
2424
fail(); // reported
2525
}
26+
27+
/**
28+
* @phpstan-ignore-next-line
29+
*/
30+
fail();
31+
fail(); // reported
32+
33+
/**
34+
* @noinspection PhpStrictTypeCheckingInspection
35+
* @phpstan-ignore-next-line
36+
*/
37+
fail(); // not reported because the ignore tag is valid
38+
39+
/**
40+
* @phpstan-ignore-next-line Some very loooooooooooooooooooooooooooooooooooooooooooon
41+
* coooooooooooooooooooooooooooooooooooooooooooooooooomment
42+
* on many lines.
43+
*/
44+
fail(); // not reported because the ignore tag is valid
45+
46+
/**
47+
* @phpstan-ignore-next-line
48+
* @var int
49+
*/
50+
fail(); // reported becase ignore tag in PHPDoc is not last
2651
}
2752

2853
}

0 commit comments

Comments
 (0)