Skip to content

Commit 9474696

Browse files
committed
Fixed ignore annotations sometimes not working because of AST
1 parent 4a98863 commit 9474696

File tree

4 files changed

+88
-4
lines changed

4 files changed

+88
-4
lines changed

conf/config.neon

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1537,6 +1537,7 @@ services:
15371537
class: PHPStan\Parser\RichParser
15381538
arguments:
15391539
parser: @currentPhpVersionPhpParser
1540+
lexer: @currentPhpVersionLexer
15401541
autowired: no
15411542

15421543
currentPhpVersionSimpleParser:

src/Analyser/FileAnalyser.php

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -69,9 +69,18 @@ public function analyseFile(
6969
if (is_file($file)) {
7070
try {
7171
$parserNodes = $this->parser->parseFile($file);
72-
$linesToIgnore = [];
72+
$linesToIgnore = $this->getLinesToIgnoreFromTokens($file, $parserNodes);
7373
$temporaryFileErrors = [];
7474
$nodeCallback = function (\PhpParser\Node $node, Scope $scope) use (&$fileErrors, &$fileDependencies, &$exportedNodes, $file, $registry, $outerNodeCallback, $analysedFiles, &$linesToIgnore, &$temporaryFileErrors): void {
75+
if ($node instanceof Node\Stmt\Trait_) {
76+
foreach (array_keys($linesToIgnore[$file] ?? []) as $lineToIgnore) {
77+
if ($lineToIgnore < $node->getStartLine() || $lineToIgnore > $node->getEndLine()) {
78+
continue;
79+
}
80+
81+
unset($linesToIgnore[$file][$lineToIgnore]);
82+
}
83+
}
7584
if ($outerNodeCallback !== null) {
7685
$outerNodeCallback($node, $scope);
7786
}
@@ -163,8 +172,16 @@ public function analyseFile(
163172
}
164173
}
165174

166-
foreach ($this->getLinesToIgnore($node) as $lineToIgnore) {
167-
$linesToIgnore[$scope->getFileDescription()][$lineToIgnore] = true;
175+
if ($scope->isInTrait()) {
176+
$sameTraitFile = $file === $scope->getTraitReflection()->getFileName();
177+
foreach ($this->getLinesToIgnore($node) as $lineToIgnore) {
178+
$linesToIgnore[$scope->getFileDescription()][$lineToIgnore] = true;
179+
if (!$sameTraitFile) {
180+
continue;
181+
}
182+
183+
unset($linesToIgnore[$file][$lineToIgnore]);
184+
}
168185
}
169186

170187
try {
@@ -277,6 +294,27 @@ private function getLinesToIgnore(Node $node): array
277294
return $lines;
278295
}
279296

297+
/**
298+
* @param string $file
299+
* @param \PhpParser\Node[] $nodes
300+
* @return array<string, array<int, true>>
301+
*/
302+
private function getLinesToIgnoreFromTokens(string $file, array $nodes): array
303+
{
304+
if (!isset($nodes[0])) {
305+
return [];
306+
}
307+
308+
/** @var int[] $tokenLines */
309+
$tokenLines = $nodes[0]->getAttribute('linesToIgnore', []);
310+
$lines = [];
311+
foreach ($tokenLines as $tokenLine) {
312+
$lines[$file][$tokenLine] = true;
313+
}
314+
315+
return $lines;
316+
}
317+
280318
private function findLineToIgnoreComment(Comment $comment): ?int
281319
{
282320
$text = $comment->getText();

src/Parser/RichParser.php

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace PHPStan\Parser;
44

55
use PhpParser\ErrorHandler\Collecting;
6+
use PhpParser\Lexer;
67
use PhpParser\NodeTraverser;
78
use PhpParser\NodeVisitor\NameResolver;
89
use PhpParser\NodeVisitor\NodeConnectingVisitor;
@@ -14,6 +15,8 @@ class RichParser implements Parser
1415

1516
private \PhpParser\Parser $parser;
1617

18+
private Lexer $lexer;
19+
1720
private NameResolver $nameResolver;
1821

1922
private NodeConnectingVisitor $nodeConnectingVisitor;
@@ -22,12 +25,14 @@ class RichParser implements Parser
2225

2326
public function __construct(
2427
\PhpParser\Parser $parser,
28+
Lexer $lexer,
2529
NameResolver $nameResolver,
2630
NodeConnectingVisitor $nodeConnectingVisitor,
2731
StatementOrderVisitor $statementOrderVisitor
2832
)
2933
{
3034
$this->parser = $parser;
35+
$this->lexer = $lexer;
3136
$this->nameResolver = $nameResolver;
3237
$this->nodeConnectingVisitor = $nodeConnectingVisitor;
3338
$this->statementOrderVisitor = $statementOrderVisitor;
@@ -54,6 +59,7 @@ public function parseString(string $sourceCode): array
5459
{
5560
$errorHandler = new Collecting();
5661
$nodes = $this->parser->parse($sourceCode, $errorHandler);
62+
$tokens = $this->lexer->getTokens();
5763
if ($errorHandler->hasErrors()) {
5864
throw new \PHPStan\Parser\ParserErrorsException($errorHandler->getErrors(), null);
5965
}
@@ -67,7 +73,45 @@ public function parseString(string $sourceCode): array
6773
$nodeTraverser->addVisitor($this->statementOrderVisitor);
6874

6975
/** @var array<\PhpParser\Node\Stmt> */
70-
return $nodeTraverser->traverse($nodes);
76+
$nodes = $nodeTraverser->traverse($nodes);
77+
if (isset($nodes[0])) {
78+
$nodes[0]->setAttribute('linesToIgnore', $this->getLinesToIgnore($tokens));
79+
}
80+
81+
return $nodes;
82+
}
83+
84+
/**
85+
* @param mixed[] $tokens
86+
* @return int[]
87+
*/
88+
private function getLinesToIgnore(array $tokens): array
89+
{
90+
$lines = [];
91+
foreach ($tokens as $token) {
92+
if (is_string($token)) {
93+
continue;
94+
}
95+
96+
$type = $token[0];
97+
if ($type !== T_COMMENT && $type !== T_DOC_COMMENT) {
98+
continue;
99+
}
100+
101+
$text = $token[1];
102+
$line = $token[2];
103+
if (strpos($text, '@phpstan-ignore-next-line') !== false) {
104+
$line++;
105+
} elseif (strpos($text, '@phpstan-ignore-line') === false) {
106+
continue;
107+
}
108+
109+
$line += substr_count($token[1], "\n");
110+
111+
$lines[] = $line;
112+
}
113+
114+
return $lines;
71115
}
72116

73117
}

tests/PHPStan/Analyser/AnalyserTest.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -514,6 +514,7 @@ private function createAnalyser(bool $reportUnmatchedIgnoredErrors): \PHPStan\An
514514
$nodeScopeResolver,
515515
new RichParser(
516516
new \PhpParser\Parser\Php7($lexer),
517+
$lexer,
517518
new \PhpParser\NodeVisitor\NameResolver(),
518519
new NodeConnectingVisitor(),
519520
new StatementOrderVisitor()

0 commit comments

Comments
 (0)