From d7726bf410048c23f26a53b87f7aa28693268ae0 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Wed, 11 Feb 2026 17:08:25 +0100 Subject: [PATCH 1/9] Create FileAnalyserCallable.php --- src/Analyser/FileAnalyserCallable.php | 285 ++++++++++++++++++++++++++ 1 file changed, 285 insertions(+) create mode 100644 src/Analyser/FileAnalyserCallable.php diff --git a/src/Analyser/FileAnalyserCallable.php b/src/Analyser/FileAnalyserCallable.php new file mode 100644 index 0000000000..2157984579 --- /dev/null +++ b/src/Analyser/FileAnalyserCallable.php @@ -0,0 +1,285 @@ + */ + private array $fileErrors = []; + + /** @var CollectorData */ + private array $fileCollectedData = []; + + private array $fileDependencies = []; + + private array $usedTraitFileDependencies = []; + + private array $exportedNodes = []; + + private array $linesToIgnore = []; + + private array $unmatchedLineIgnores = []; + + private array $temporaryFileErrors = []; + + private array $processedFiles = []; + + /** + * @param array $analysedFiles + * @param callable(Node $node, Scope $scope): void|null $outerNodeCallback + * @param Node\Stmt[] $parserNodes + * @param IgnoreErrorExtension[] $ignoreErrorExtensions + */ + public function __construct( + private string $file, + private array $analysedFiles, + private RuleRegistry $ruleRegistry, + private CollectorRegistry $collectorRegistry, + private $outerNodeCallback, + private array $parserNodes, + private array $ignoreErrorExtensions, + private Parser $parser, + private DependencyResolver $dependencyResolver, + private RuleErrorTransformer $ruleErrorTransformer, + ) + { + } + + public function __invoke(Node $node, Scope $scope): void + { + $parserNodes = $this->parserNodes; + + /** @var Scope&NodeCallbackInvoker $scope */ + if ($node instanceof Node\Stmt\Trait_) { + foreach (array_keys($this->linesToIgnore[$this->file] ?? []) as $lineToIgnore) { + if ($lineToIgnore < $node->getStartLine() || $lineToIgnore > $node->getEndLine()) { + continue; + } + + unset($this->unmatchedLineIgnores[$this->file][$lineToIgnore]); + } + } + if ($node instanceof InTraitNode) { + $traitNode = $node->getOriginalNode(); + $fileDescription = $scope->getFileDescription(); + $this->linesToIgnore[$fileDescription] ??= []; + $this->linesToIgnore[$fileDescription] += $this->getLinesToIgnoreFromTokens([$traitNode]); + + $traitFileName = $node->getTraitReflection()->getFileName(); + if ($traitFileName !== null) { + $this->processedFiles[] = $traitFileName; + } + } + + if ($scope->isInTrait()) { + $traitReflection = $scope->getTraitReflection(); + if ($traitReflection->getFileName() !== null) { + $traitFilePath = $traitReflection->getFileName(); + $parserNodes = $this->parser->parseFile($traitFilePath); + } + } + + if ($this->outerNodeCallback !== null) { + ($this->outerNodeCallback)($node, $scope); + } + $uniquedAnalysedCodeExceptionMessages = []; + $nodeType = get_class($node); + foreach ($this->ruleRegistry->getRules($nodeType) as $rule) { + try { + $ruleErrors = $rule->processNode($node, $scope); + } catch (AnalysedCodeException $e) { + if (isset($uniquedAnalysedCodeExceptionMessages[$e->getMessage()])) { + continue; + } + + $uniquedAnalysedCodeExceptionMessages[$e->getMessage()] = true; + $this->fileErrors[] = (new Error($e->getMessage(), $this->file, $node->getStartLine(), $e, tip: $e->getTip())) + ->withIdentifier('phpstan.internal') + ->withMetadata([ + InternalError::STACK_TRACE_METADATA_KEY => InternalError::prepareTrace($e), + InternalError::STACK_TRACE_AS_STRING_METADATA_KEY => $e->getTraceAsString(), + ]); + continue; + } catch (IdentifierNotFound $e) { + $this->fileErrors[] = (new Error(sprintf('Reflection error: %s not found.', $e->getIdentifier()->getName()), $this->file, $node->getStartLine(), $e, tip: 'Learn more at https://phpstan.org/user-guide/discovering-symbols')) + ->withIdentifier('phpstan.reflection') + ->withMetadata([ + InternalError::STACK_TRACE_METADATA_KEY => InternalError::prepareTrace($e), + InternalError::STACK_TRACE_AS_STRING_METADATA_KEY => $e->getTraceAsString(), + ]); + continue; + } catch (UnableToCompileNode | CircularReference $e) { + $this->fileErrors[] = (new Error(sprintf('Reflection error: %s', $e->getMessage()), $this->file, $node->getStartLine(), $e)) + ->withIdentifier('phpstan.reflection') + ->withMetadata([ + InternalError::STACK_TRACE_METADATA_KEY => InternalError::prepareTrace($e), + InternalError::STACK_TRACE_AS_STRING_METADATA_KEY => $e->getTraceAsString(), + ]); + continue; + } + + foreach ($ruleErrors as $ruleError) { + $error = $this->ruleErrorTransformer->transform($ruleError, $scope, $parserNodes, $node); + + if ($error->canBeIgnored()) { + foreach ($this->ignoreErrorExtensions as $ignoreErrorExtension) { + if ($ignoreErrorExtension->shouldIgnore($error, $node, $scope)) { + continue 2; + } + } + } + + $this->temporaryFileErrors[] = $error; + } + } + + foreach ($this->collectorRegistry->getCollectors($nodeType) as $collector) { + try { + $collectedData = $collector->processNode($node, $scope); + } catch (AnalysedCodeException $e) { + if (isset($uniquedAnalysedCodeExceptionMessages[$e->getMessage()])) { + continue; + } + + $uniquedAnalysedCodeExceptionMessages[$e->getMessage()] = true; + $this->fileErrors[] = (new Error($e->getMessage(), $this->file, $node->getStartLine(), $e, tip: $e->getTip())) + ->withIdentifier('phpstan.internal') + ->withMetadata([ + InternalError::STACK_TRACE_METADATA_KEY => InternalError::prepareTrace($e), + InternalError::STACK_TRACE_AS_STRING_METADATA_KEY => $e->getTraceAsString(), + ]); + continue; + } catch (IdentifierNotFound $e) { + $this->fileErrors[] = (new Error(sprintf('Reflection error: %s not found.', $e->getIdentifier()->getName()), $this->file, $node->getStartLine(), $e, tip: 'Learn more at https://phpstan.org/user-guide/discovering-symbols')) + ->withIdentifier('phpstan.reflection') + ->withMetadata([ + InternalError::STACK_TRACE_METADATA_KEY => InternalError::prepareTrace($e), + InternalError::STACK_TRACE_AS_STRING_METADATA_KEY => $e->getTraceAsString(), + ]); + continue; + } catch (UnableToCompileNode | CircularReference $e) { + $this->fileErrors[] = (new Error(sprintf('Reflection error: %s', $e->getMessage()), $this->file, $node->getStartLine(), $e)) + ->withIdentifier('phpstan.reflection') + ->withMetadata([ + InternalError::STACK_TRACE_METADATA_KEY => InternalError::prepareTrace($e), + InternalError::STACK_TRACE_AS_STRING_METADATA_KEY => $e->getTraceAsString(), + ]); + continue; + } + + if ($collectedData === null) { + continue; + } + + $this->fileCollectedData[$scope->getFile()][get_class($collector)][] = $collectedData; + } + + try { + $dependencies = $this->dependencyResolver->resolveDependencies($node, $scope); + foreach ($dependencies->getFileDependencies($scope->getFile(), $this->analysedFiles) as $dependentFile) { + $this->fileDependencies[] = $dependentFile; + } + if ($dependencies->getExportedNode() !== null) { + $this->exportedNodes[] = $dependencies->getExportedNode(); + } + } catch (AnalysedCodeException) { + // pass + } catch (IdentifierNotFound) { + // pass + } catch (UnableToCompileNode) { + // pass + } + + if (!$node instanceof InClassNode) { + return; + } + + $usedTraitDependencies = $this->dependencyResolver->resolveUsedTraitDependencies($node); + foreach ($usedTraitDependencies->getFileDependencies($scope->getFile(), $this->analysedFiles) as $dependentFile) { + $this->usedTraitFileDependencies[] = $dependentFile; + } + } + + /** + * @param Node[] $nodes + * @return array|null> + */ + private function getLinesToIgnoreFromTokens(array $nodes): array + { + if (!isset($nodes[0])) { + return []; + } + + /** @var array|null> */ + return $nodes[0]->getAttribute('linesToIgnore', []); + } + + /** + * @return list + */ + public function getFileErrors(): array + { + return $this->fileErrors; + } + + public function getFileCollectedData(): array + { + return $this->fileCollectedData; + } + + public function getFileDependencies(): array + { + return $this->fileDependencies; + } + + public function getUsedTraitFileDependencies(): array + { + return $this->usedTraitFileDependencies; + } + + public function getExportedNodes(): array + { + return $this->exportedNodes; + } + + public function getLinesToIgnore(): array + { + return $this->linesToIgnore; + } + + public function getUnmatchedLineIgnores(): array + { + return $this->unmatchedLineIgnores; + } + + public function getTemporaryFileErrors(): array + { + return $this->temporaryFileErrors; + } + + public function getProcessedFiles(): array + { + return $this->processedFiles; + } + +} From 0c0c3d9202fbb897c75ae312f4bbd518a960e826 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Wed, 11 Feb 2026 17:17:23 +0100 Subject: [PATCH 2/9] Update FileAnalyser.php --- src/Analyser/FileAnalyser.php | 177 ++++------------------------------ 1 file changed, 21 insertions(+), 156 deletions(-) diff --git a/src/Analyser/FileAnalyser.php b/src/Analyser/FileAnalyser.php index 4fbb1c89c9..95028f819b 100644 --- a/src/Analyser/FileAnalyser.php +++ b/src/Analyser/FileAnalyser.php @@ -13,17 +13,13 @@ use PHPStan\DependencyInjection\AutowiredParameter; use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Node\FileNode; -use PHPStan\Node\InClassNode; -use PHPStan\Node\InTraitNode; use PHPStan\Parser\Parser; use PHPStan\Parser\ParserErrorsException; use PHPStan\Rules\Registry as RuleRegistry; -use function array_keys; use function array_unique; use function array_values; use function count; use function error_reporting; -use function get_class; use function hash; use function is_dir; use function is_file; @@ -104,159 +100,19 @@ public function analyseFile( $processedFiles[] = $file; $linesToIgnore = $unmatchedLineIgnores = [$file => $this->getLinesToIgnoreFromTokens($parserNodes)]; $ignoreErrorExtensions = $this->ignoreErrorExtensionProvider->getExtensions(); - $temporaryFileErrors = []; - $nodeCallback = function (Node $node, $scope) use (&$fileErrors, &$fileCollectedData, &$fileDependencies, &$usedTraitFileDependencies, &$exportedNodes, $file, $ruleRegistry, $collectorRegistry, $outerNodeCallback, $analysedFiles, &$linesToIgnore, &$unmatchedLineIgnores, &$temporaryFileErrors, &$processedFiles, $parserNodes, $ignoreErrorExtensions): void { - /** @var Scope&NodeCallbackInvoker $scope */ - if ($node instanceof Node\Stmt\Trait_) { - foreach (array_keys($linesToIgnore[$file] ?? []) as $lineToIgnore) { - if ($lineToIgnore < $node->getStartLine() || $lineToIgnore > $node->getEndLine()) { - continue; - } - - unset($unmatchedLineIgnores[$file][$lineToIgnore]); - } - } - if ($node instanceof InTraitNode) { - $traitNode = $node->getOriginalNode(); - $fileDescription = $scope->getFileDescription(); - $linesToIgnore[$fileDescription] ??= []; - $linesToIgnore[$fileDescription] += $this->getLinesToIgnoreFromTokens([$traitNode]); - - $traitFileName = $node->getTraitReflection()->getFileName(); - if ($traitFileName !== null) { - $processedFiles[] = $traitFileName; - } - } - - if ($scope->isInTrait()) { - $traitReflection = $scope->getTraitReflection(); - if ($traitReflection->getFileName() !== null) { - $traitFilePath = $traitReflection->getFileName(); - $parserNodes = $this->parser->parseFile($traitFilePath); - } - } - - if ($outerNodeCallback !== null) { - $outerNodeCallback($node, $scope); - } - $uniquedAnalysedCodeExceptionMessages = []; - $nodeType = get_class($node); - foreach ($ruleRegistry->getRules($nodeType) as $rule) { - try { - $ruleErrors = $rule->processNode($node, $scope); - } catch (AnalysedCodeException $e) { - if (isset($uniquedAnalysedCodeExceptionMessages[$e->getMessage()])) { - continue; - } - - $uniquedAnalysedCodeExceptionMessages[$e->getMessage()] = true; - $fileErrors[] = (new Error($e->getMessage(), $file, $node->getStartLine(), $e, tip: $e->getTip())) - ->withIdentifier('phpstan.internal') - ->withMetadata([ - InternalError::STACK_TRACE_METADATA_KEY => InternalError::prepareTrace($e), - InternalError::STACK_TRACE_AS_STRING_METADATA_KEY => $e->getTraceAsString(), - ]); - continue; - } catch (IdentifierNotFound $e) { - $fileErrors[] = (new Error(sprintf('Reflection error: %s not found.', $e->getIdentifier()->getName()), $file, $node->getStartLine(), $e, tip: 'Learn more at https://phpstan.org/user-guide/discovering-symbols')) - ->withIdentifier('phpstan.reflection') - ->withMetadata([ - InternalError::STACK_TRACE_METADATA_KEY => InternalError::prepareTrace($e), - InternalError::STACK_TRACE_AS_STRING_METADATA_KEY => $e->getTraceAsString(), - ]); - continue; - } catch (UnableToCompileNode | CircularReference $e) { - $fileErrors[] = (new Error(sprintf('Reflection error: %s', $e->getMessage()), $file, $node->getStartLine(), $e)) - ->withIdentifier('phpstan.reflection') - ->withMetadata([ - InternalError::STACK_TRACE_METADATA_KEY => InternalError::prepareTrace($e), - InternalError::STACK_TRACE_AS_STRING_METADATA_KEY => $e->getTraceAsString(), - ]); - continue; - } - - foreach ($ruleErrors as $ruleError) { - $error = $this->ruleErrorTransformer->transform($ruleError, $scope, $parserNodes, $node); - - if ($error->canBeIgnored()) { - foreach ($ignoreErrorExtensions as $ignoreErrorExtension) { - if ($ignoreErrorExtension->shouldIgnore($error, $node, $scope)) { - continue 2; - } - } - } - - $temporaryFileErrors[] = $error; - } - } - - foreach ($collectorRegistry->getCollectors($nodeType) as $collector) { - try { - $collectedData = $collector->processNode($node, $scope); - } catch (AnalysedCodeException $e) { - if (isset($uniquedAnalysedCodeExceptionMessages[$e->getMessage()])) { - continue; - } - - $uniquedAnalysedCodeExceptionMessages[$e->getMessage()] = true; - $fileErrors[] = (new Error($e->getMessage(), $file, $node->getStartLine(), $e, tip: $e->getTip())) - ->withIdentifier('phpstan.internal') - ->withMetadata([ - InternalError::STACK_TRACE_METADATA_KEY => InternalError::prepareTrace($e), - InternalError::STACK_TRACE_AS_STRING_METADATA_KEY => $e->getTraceAsString(), - ]); - continue; - } catch (IdentifierNotFound $e) { - $fileErrors[] = (new Error(sprintf('Reflection error: %s not found.', $e->getIdentifier()->getName()), $file, $node->getStartLine(), $e, tip: 'Learn more at https://phpstan.org/user-guide/discovering-symbols')) - ->withIdentifier('phpstan.reflection') - ->withMetadata([ - InternalError::STACK_TRACE_METADATA_KEY => InternalError::prepareTrace($e), - InternalError::STACK_TRACE_AS_STRING_METADATA_KEY => $e->getTraceAsString(), - ]); - continue; - } catch (UnableToCompileNode | CircularReference $e) { - $fileErrors[] = (new Error(sprintf('Reflection error: %s', $e->getMessage()), $file, $node->getStartLine(), $e)) - ->withIdentifier('phpstan.reflection') - ->withMetadata([ - InternalError::STACK_TRACE_METADATA_KEY => InternalError::prepareTrace($e), - InternalError::STACK_TRACE_AS_STRING_METADATA_KEY => $e->getTraceAsString(), - ]); - continue; - } - - if ($collectedData === null) { - continue; - } - - $fileCollectedData[$scope->getFile()][get_class($collector)][] = $collectedData; - } - - try { - $dependencies = $this->dependencyResolver->resolveDependencies($node, $scope); - foreach ($dependencies->getFileDependencies($scope->getFile(), $analysedFiles) as $dependentFile) { - $fileDependencies[] = $dependentFile; - } - if ($dependencies->getExportedNode() !== null) { - $exportedNodes[] = $dependencies->getExportedNode(); - } - } catch (AnalysedCodeException) { - // pass - } catch (IdentifierNotFound) { - // pass - } catch (UnableToCompileNode) { - // pass - } - - if (!$node instanceof InClassNode) { - return; - } - - $usedTraitDependencies = $this->dependencyResolver->resolveUsedTraitDependencies($node); - foreach ($usedTraitDependencies->getFileDependencies($scope->getFile(), $analysedFiles) as $dependentFile) { - $usedTraitFileDependencies[] = $dependentFile; - } - }; + $nodeCallback = new FileAnalyserCallable( + $file, + $analysedFiles, + $ruleRegistry, + $collectorRegistry, + $outerNodeCallback, + $parserNodes, + $ignoreErrorExtensions, + $this->parser, + $this->dependencyResolver, + $this->ruleErrorTransformer, + ); $scope = $this->scopeFactory->create(ScopeContext::create($file), $nodeCallback); $nodeCallback(new FileNode($parserNodes), $scope); $this->nodeScopeResolver->processNodes( @@ -264,6 +120,15 @@ public function analyseFile( $scope, $nodeCallback, ); + $fileErrors = $nodeCallback->getFileErrors(); + $fileCollectedData = $nodeCallback->getFileCollectedData(); + $fileDependencies = $nodeCallback->getFileDependencies(); + $usedTraitFileDependencies = $nodeCallback->getUsedTraitFileDependencies(); + $exportedNodes = $nodeCallback->getExportedNodes(); + $linesToIgnore = $nodeCallback->getLinesToIgnore(); + $unmatchedLineIgnores = $nodeCallback->getUnmatchedLineIgnores(); + $temporaryFileErrors = $nodeCallback->getTemporaryFileErrors(); + $processedFiles = $nodeCallback->getProcessedFiles(); $localIgnoresProcessorResult = $this->localIgnoresProcessor->process( $temporaryFileErrors, From c786014350376d54b3c51210713aa6f525fcabad Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Wed, 11 Feb 2026 17:25:06 +0100 Subject: [PATCH 3/9] fix --- src/Analyser/FileAnalyser.php | 3 +++ src/Analyser/FileAnalyserCallable.php | 8 +++----- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/Analyser/FileAnalyser.php b/src/Analyser/FileAnalyser.php index 95028f819b..a3e921a4d5 100644 --- a/src/Analyser/FileAnalyser.php +++ b/src/Analyser/FileAnalyser.php @@ -112,6 +112,9 @@ public function analyseFile( $this->parser, $this->dependencyResolver, $this->ruleErrorTransformer, + $linesToIgnore, + $unmatchedLineIgnores, + $processedFiles, ); $scope = $this->scopeFactory->create(ScopeContext::create($file), $nodeCallback); $nodeCallback(new FileNode($parserNodes), $scope); diff --git a/src/Analyser/FileAnalyserCallable.php b/src/Analyser/FileAnalyserCallable.php index 2157984579..1370047154 100644 --- a/src/Analyser/FileAnalyserCallable.php +++ b/src/Analyser/FileAnalyserCallable.php @@ -36,13 +36,8 @@ final class FileAnalyserCallable private array $exportedNodes = []; - private array $linesToIgnore = []; - - private array $unmatchedLineIgnores = []; - private array $temporaryFileErrors = []; - private array $processedFiles = []; /** * @param array $analysedFiles @@ -61,6 +56,9 @@ public function __construct( private Parser $parser, private DependencyResolver $dependencyResolver, private RuleErrorTransformer $ruleErrorTransformer, + private array $linesToIgnore, + private array $unmatchedLineIgnores, + private array $processedFiles ) { } From db5d390f568ef74ea904abad6c4f0af9291318ab Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Wed, 11 Feb 2026 17:25:33 +0100 Subject: [PATCH 4/9] Update FileAnalyserCallable.php --- src/Analyser/FileAnalyserCallable.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Analyser/FileAnalyserCallable.php b/src/Analyser/FileAnalyserCallable.php index 1370047154..a8e7cda159 100644 --- a/src/Analyser/FileAnalyserCallable.php +++ b/src/Analyser/FileAnalyserCallable.php @@ -38,7 +38,6 @@ final class FileAnalyserCallable private array $temporaryFileErrors = []; - /** * @param array $analysedFiles * @param callable(Node $node, Scope $scope): void|null $outerNodeCallback @@ -58,7 +57,7 @@ public function __construct( private RuleErrorTransformer $ruleErrorTransformer, private array $linesToIgnore, private array $unmatchedLineIgnores, - private array $processedFiles + private array $processedFiles, ) { } From e502fd3cf0e4ea32ed229a6798dc1d09d783295a Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Wed, 11 Feb 2026 17:26:57 +0100 Subject: [PATCH 5/9] Update FileAnalyserCallback.php --- .../{FileAnalyserCallable.php => FileAnalyserCallback.php} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename src/Analyser/{FileAnalyserCallable.php => FileAnalyserCallback.php} (99%) diff --git a/src/Analyser/FileAnalyserCallable.php b/src/Analyser/FileAnalyserCallback.php similarity index 99% rename from src/Analyser/FileAnalyserCallable.php rename to src/Analyser/FileAnalyserCallback.php index a8e7cda159..9bb01cd0ca 100644 --- a/src/Analyser/FileAnalyserCallable.php +++ b/src/Analyser/FileAnalyserCallback.php @@ -21,7 +21,7 @@ /** * @phpstan-import-type CollectorData from CollectedData */ -final class FileAnalyserCallable +final class FileAnalyserCallback { /** @var list */ From 41308ecd344f77931f3337acaa254a3aa3fdf01a Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Wed, 11 Feb 2026 17:26:59 +0100 Subject: [PATCH 6/9] Update FileAnalyser.php --- src/Analyser/FileAnalyser.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Analyser/FileAnalyser.php b/src/Analyser/FileAnalyser.php index a3e921a4d5..c9cce73487 100644 --- a/src/Analyser/FileAnalyser.php +++ b/src/Analyser/FileAnalyser.php @@ -101,7 +101,7 @@ public function analyseFile( $linesToIgnore = $unmatchedLineIgnores = [$file => $this->getLinesToIgnoreFromTokens($parserNodes)]; $ignoreErrorExtensions = $this->ignoreErrorExtensionProvider->getExtensions(); - $nodeCallback = new FileAnalyserCallable( + $nodeCallback = new FileAnalyserCallback( $file, $analysedFiles, $ruleRegistry, From cd59eee0445108c38f247ca718d99cd38e083615 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Wed, 11 Feb 2026 17:31:32 +0100 Subject: [PATCH 7/9] fix --- src/Analyser/FileAnalyser.php | 20 +------------------- src/Analyser/FileAnalyserCallback.php | 6 ++++-- 2 files changed, 5 insertions(+), 21 deletions(-) diff --git a/src/Analyser/FileAnalyser.php b/src/Analyser/FileAnalyser.php index c9cce73487..7748903cbf 100644 --- a/src/Analyser/FileAnalyser.php +++ b/src/Analyser/FileAnalyser.php @@ -98,8 +98,6 @@ public function analyseFile( $this->collectErrors($analysedFiles); $parserNodes = $this->parser->parseFile($file); $processedFiles[] = $file; - $linesToIgnore = $unmatchedLineIgnores = [$file => $this->getLinesToIgnoreFromTokens($parserNodes)]; - $ignoreErrorExtensions = $this->ignoreErrorExtensionProvider->getExtensions(); $nodeCallback = new FileAnalyserCallback( $file, @@ -108,12 +106,10 @@ public function analyseFile( $collectorRegistry, $outerNodeCallback, $parserNodes, - $ignoreErrorExtensions, + $this->ignoreErrorExtensionProvider->getExtensions(), $this->parser, $this->dependencyResolver, $this->ruleErrorTransformer, - $linesToIgnore, - $unmatchedLineIgnores, $processedFiles, ); $scope = $this->scopeFactory->create(ScopeContext::create($file), $nodeCallback); @@ -213,20 +209,6 @@ public function analyseFile( ); } - /** - * @param Node[] $nodes - * @return array|null> - */ - private function getLinesToIgnoreFromTokens(array $nodes): array - { - if (!isset($nodes[0])) { - return []; - } - - /** @var array|null> */ - return $nodes[0]->getAttribute('linesToIgnore', []); - } - /** * @param array $analysedFiles */ diff --git a/src/Analyser/FileAnalyserCallback.php b/src/Analyser/FileAnalyserCallback.php index 9bb01cd0ca..2cc389a3dc 100644 --- a/src/Analyser/FileAnalyserCallback.php +++ b/src/Analyser/FileAnalyserCallback.php @@ -38,6 +38,9 @@ final class FileAnalyserCallback private array $temporaryFileErrors = []; + private array $linesToIgnore; + private array $unmatchedLineIgnores; + /** * @param array $analysedFiles * @param callable(Node $node, Scope $scope): void|null $outerNodeCallback @@ -55,11 +58,10 @@ public function __construct( private Parser $parser, private DependencyResolver $dependencyResolver, private RuleErrorTransformer $ruleErrorTransformer, - private array $linesToIgnore, - private array $unmatchedLineIgnores, private array $processedFiles, ) { + $this->linesToIgnore = $this->unmatchedLineIgnores = [$file => $this->getLinesToIgnoreFromTokens($parserNodes)]; } public function __invoke(Node $node, Scope $scope): void From a701570fa97d56bb172fe0fa3e8ae0c76dfd7f7d Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Wed, 11 Feb 2026 17:32:20 +0100 Subject: [PATCH 8/9] Update FileAnalyserCallback.php --- src/Analyser/FileAnalyserCallback.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Analyser/FileAnalyserCallback.php b/src/Analyser/FileAnalyserCallback.php index 2cc389a3dc..3a6afdcb6c 100644 --- a/src/Analyser/FileAnalyserCallback.php +++ b/src/Analyser/FileAnalyserCallback.php @@ -39,6 +39,7 @@ final class FileAnalyserCallback private array $temporaryFileErrors = []; private array $linesToIgnore; + private array $unmatchedLineIgnores; /** From c8bb228a395059a21a9bfbcc8b45c9c16bfb599a Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Wed, 11 Feb 2026 17:42:31 +0100 Subject: [PATCH 9/9] cs --- src/Analyser/FileAnalyserCallback.php | 33 +++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/src/Analyser/FileAnalyserCallback.php b/src/Analyser/FileAnalyserCallback.php index 3a6afdcb6c..474ccee859 100644 --- a/src/Analyser/FileAnalyserCallback.php +++ b/src/Analyser/FileAnalyserCallback.php @@ -10,6 +10,7 @@ use PHPStan\Collectors\CollectedData; use PHPStan\Collectors\Registry as CollectorRegistry; use PHPStan\Dependency\DependencyResolver; +use PHPStan\Dependency\RootExportedNode; use PHPStan\Node\InClassNode; use PHPStan\Node\InTraitNode; use PHPStan\Parser\Parser; @@ -20,6 +21,7 @@ /** * @phpstan-import-type CollectorData from CollectedData + * @phpstan-import-type LinesToIgnore from FileAnalyserResult */ final class FileAnalyserCallback { @@ -30,16 +32,22 @@ final class FileAnalyserCallback /** @var CollectorData */ private array $fileCollectedData = []; + /** @var array */ private array $fileDependencies = []; + /** @var array */ private array $usedTraitFileDependencies = []; + /** @var list */ private array $exportedNodes = []; + /** @var list */ private array $temporaryFileErrors = []; + /** @var LinesToIgnore */ private array $linesToIgnore; + /** @var LinesToIgnore */ private array $unmatchedLineIgnores; /** @@ -47,6 +55,7 @@ final class FileAnalyserCallback * @param callable(Node $node, Scope $scope): void|null $outerNodeCallback * @param Node\Stmt[] $parserNodes * @param IgnoreErrorExtension[] $ignoreErrorExtensions + * @param list $processedFiles */ public function __construct( private string $file, @@ -242,41 +251,65 @@ public function getFileErrors(): array return $this->fileErrors; } + /** + * @return CollectorData + */ public function getFileCollectedData(): array { return $this->fileCollectedData; } + /** + * @return array + */ public function getFileDependencies(): array { return $this->fileDependencies; } + /** + * @return array + */ public function getUsedTraitFileDependencies(): array { return $this->usedTraitFileDependencies; } + /** + * @return list + */ public function getExportedNodes(): array { return $this->exportedNodes; } + /** + * @return LinesToIgnore + */ public function getLinesToIgnore(): array { return $this->linesToIgnore; } + /** + * @return LinesToIgnore + */ public function getUnmatchedLineIgnores(): array { return $this->unmatchedLineIgnores; } + /** + * @return list + */ public function getTemporaryFileErrors(): array { return $this->temporaryFileErrors; } + /** + * @return list + */ public function getProcessedFiles(): array { return $this->processedFiles;