Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions src/Analyser/Analyser.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,17 @@ public function __construct(
* @param Closure(string $file): void|null $preFileCallback
* @param Closure(int, list<string>=): void|null $postFileCallback
* @param string[]|null $allAnalysedFiles
* @param string|null $tmpFile editor mode (--tmp-file): the temp file present in $files whose content is analysed
* @param string|null $insteadOfFile editor mode (--instead-of): the real path $tmpFile should be reported under
*/
public function analyse(
array $files,
?Closure $preFileCallback = null,
?Closure $postFileCallback = null,
bool $debug = false,
?array $allAnalysedFiles = null,
?string $tmpFile = null,
?string $insteadOfFile = null,
): AnalyserResult
{
if ($allAnalysedFiles === null) {
Expand Down Expand Up @@ -83,12 +87,16 @@ public function analyse(
}

try {
$reportedFile = $tmpFile !== null && $insteadOfFile !== null && $file === $tmpFile
? $insteadOfFile
: null;
$fileAnalyserResult = $this->fileAnalyser->analyseFile(
$file,
$allAnalysedFiles,
$this->ruleRegistry,
$this->collectorRegistry,
null,
$reportedFile,
);
$errors = array_merge($errors, $fileAnalyserResult->getErrors());
$filteredPhpErrors = array_merge($filteredPhpErrors, $fileAnalyserResult->getFilteredPhpErrors());
Expand Down
47 changes: 47 additions & 0 deletions src/Analyser/EditorModeFileHelper.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<?php declare(strict_types = 1);

namespace PHPStan\Analyser;

use PHPStan\DependencyInjection\AutowiredParameter;
use PHPStan\DependencyInjection\AutowiredService;

/**
* Editor mode (--tmp-file / --instead-of) analyses the content of the temp file
* but reports it under the real (--instead-of) path, so that Scope::getFile() and
* anything derived from it matches a normal analysis run.
*
* Internal machinery that reads/parses/reflects the file by its Scope path still
* needs the temp file (its content, its reflection, its cache identity). This helper
* maps a reported path back to the file whose content is actually analysed.
*/
#[AutowiredService]
final class EditorModeFileHelper
{

public function __construct(
#[AutowiredParameter]
private ?string $singleReflectionFile,
#[AutowiredParameter]
private ?string $singleReflectionInsteadOfFile,
)
{
}

/**
* Maps the reported (real) file path back to the file whose content is analysed.
* Returns $reportedFile unchanged outside of editor mode.
*/
public function getAnalysedFile(string $reportedFile): string
{
if (
$this->singleReflectionFile !== null
&& $this->singleReflectionInsteadOfFile !== null
&& $reportedFile === $this->singleReflectionInsteadOfFile
) {
return $this->singleReflectionFile;
}

return $reportedFile;
}

}
30 changes: 17 additions & 13 deletions src/Analyser/FileAnalyser.php
Original file line number Diff line number Diff line change
Expand Up @@ -71,15 +71,19 @@ public function __construct(
/**
* @param array<string, true> $analysedFiles
* @param callable(Node $node, Scope $scope): void|null $outerNodeCallback
* @param string|null $reportedFile the path the file should be reported under; differs from $file in editor mode (--tmp-file / --instead-of), where $file is the temp file being read and $reportedFile is the real path
*/
public function analyseFile(
string $file,
array $analysedFiles,
RuleRegistry $ruleRegistry,
CollectorRegistry $collectorRegistry,
?callable $outerNodeCallback,
?string $reportedFile = null,
): FileAnalyserResult
{
$analysisFile = $file;
$reportedFile ??= $file;
/** @var list<Error> $fileErrors */
$fileErrors = [];

Expand All @@ -98,14 +102,14 @@ public function analyseFile(
$exportedNodes = [];
$linesToIgnore = [];
$unmatchedLineIgnores = [];
if (is_file($file)) {
if (is_file($analysisFile)) {
try {
$this->collectErrors($analysedFiles);
$parserNodes = $this->parser->parseFile($file);
$processedFiles[] = $file;
$parserNodes = $this->parser->parseFile($analysisFile);
$processedFiles[] = $reportedFile;

$nodeCallback = new FileAnalyserCallback(
$file,
$reportedFile,
$analysedFiles,
$ruleRegistry,
$collectorRegistry,
Expand All @@ -118,7 +122,7 @@ public function analyseFile(
$this->ruleErrorTransformer,
$processedFiles,
);
$scope = $this->scopeFactory->create(ScopeContext::create($file), $nodeCallback);
$scope = $this->scopeFactory->create(ScopeContext::create($reportedFile, $analysisFile), $nodeCallback);
$nodeCallback(new FileNode($parserNodes), $scope);
$this->nodeScopeResolver->processNodes(
$parserNodes,
Expand Down Expand Up @@ -189,27 +193,27 @@ public function analyseFile(
$linesToIgnore = $localIgnoresProcessorResult->getLinesToIgnore();
$unmatchedLineIgnores = $localIgnoresProcessorResult->getUnmatchedLineIgnores();
} catch (\PhpParser\Error $e) {
$fileErrors[] = (new Error($e->getRawMessage(), $file, $e->getStartLine() !== -1 ? $e->getStartLine() : null, $e))->withIdentifier('phpstan.parse');
$fileErrors[] = (new Error($e->getRawMessage(), $reportedFile, $e->getStartLine() !== -1 ? $e->getStartLine() : null, $e))->withIdentifier('phpstan.parse');
} catch (ParserErrorsException $e) {
foreach ($e->getErrors() as $error) {
$fileErrors[] = (new Error($error->getMessage(), $e->getParsedFile() ?? $file, $error->getLine() !== -1 ? $error->getStartLine() : null, $e))->withIdentifier('phpstan.parse');
$fileErrors[] = (new Error($error->getMessage(), $e->getParsedFile() === $analysisFile || $e->getParsedFile() === null ? $reportedFile : $e->getParsedFile(), $error->getLine() !== -1 ? $error->getStartLine() : null, $e))->withIdentifier('phpstan.parse');
}
} catch (AnalysedCodeException $e) {
$fileErrors[] = (new Error($e->getMessage(), $file, canBeIgnored: $e, tip: $e->getTip()))
$fileErrors[] = (new Error($e->getMessage(), $reportedFile, canBeIgnored: $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(),
]);
} catch (IdentifierNotFound $e) {
$fileErrors[] = (new Error(sprintf('Reflection error: %s not found.', $e->getIdentifier()->getName()), $file, canBeIgnored: $e, tip: 'Learn more at https://phpstan.org/user-guide/discovering-symbols'))
$fileErrors[] = (new Error(sprintf('Reflection error: %s not found.', $e->getIdentifier()->getName()), $reportedFile, canBeIgnored: $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(),
]);
} catch (UnableToCompileNode | CircularReference $e) {
$fileErrors[] = (new Error(sprintf('Reflection error: %s', $e->getMessage()), $file, canBeIgnored: $e))
$fileErrors[] = (new Error(sprintf('Reflection error: %s', $e->getMessage()), $reportedFile, canBeIgnored: $e))
->withIdentifier('phpstan.reflection')
->withMetadata([
InternalError::STACK_TRACE_METADATA_KEY => InternalError::prepareTrace($e),
Expand All @@ -218,10 +222,10 @@ public function analyseFile(
} finally {
$this->restoreCollectErrorsHandler();
}
} elseif (is_dir($file)) {
$fileErrors[] = (new Error(sprintf('File %s is a directory.', $file), $file, canBeIgnored: false))->withIdentifier('phpstan.path');
} elseif (is_dir($analysisFile)) {
$fileErrors[] = (new Error(sprintf('File %s is a directory.', $reportedFile), $reportedFile, canBeIgnored: false))->withIdentifier('phpstan.path');
} else {
$fileErrors[] = (new Error(sprintf('File %s does not exist.', $file), $file, canBeIgnored: false))->withIdentifier('phpstan.path');
$fileErrors[] = (new Error(sprintf('File %s does not exist.', $reportedFile), $reportedFile, canBeIgnored: false))->withIdentifier('phpstan.path');
}

foreach ($linesToIgnore as $fileKey => $lines) {
Expand Down
12 changes: 11 additions & 1 deletion src/Analyser/MutatingScope.php
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,16 @@ public function getFile(): string
return $this->context->getFile();
}

/**
* The file whose content is actually analysed. Equal to getFile() except in
* editor mode (--tmp-file / --instead-of), where getFile() is the real path
* while this is the temp file being read.
*/
public function getAnalysisFile(): string
{
return $this->context->getAnalysisFile();
}

/** @api */
public function getFileDescription(): string
{
Expand Down Expand Up @@ -919,7 +929,7 @@ public function hasConstant(Name $name): bool

private function fileHasCompilerHaltStatementCalls(): bool
{
$nodes = $this->parser->parseFile($this->getFile());
$nodes = $this->parser->parseFile($this->getAnalysisFile());
foreach ($nodes as $node) {
if ($node instanceof Node\Stmt\HaltCompiler) {
return true;
Expand Down
11 changes: 6 additions & 5 deletions src/Analyser/NodeScopeResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -2627,14 +2627,14 @@ private function getOverridingThrowPoints(Node\Stmt $statement, MutatingScope $s
return null;
}

private function getCurrentClassReflection(Node\Stmt\ClassLike $stmt, string $className, Scope $scope): ClassReflection
private function getCurrentClassReflection(Node\Stmt\ClassLike $stmt, string $className, MutatingScope $scope): ClassReflection
{
if (!$this->reflectionProvider->hasClass($className)) {
return $this->createAstClassReflection($stmt, $className, $scope);
}

$defaultClassReflection = $this->reflectionProvider->getClass($className);
if ($defaultClassReflection->getFileName() !== $scope->getFile()) {
if ($defaultClassReflection->getFileName() !== $scope->getAnalysisFile()) {
return $this->createAstClassReflection($stmt, $className, $scope);
}

Expand All @@ -2646,13 +2646,14 @@ private function getCurrentClassReflection(Node\Stmt\ClassLike $stmt, string $cl
return $defaultClassReflection;
}

private function createAstClassReflection(Node\Stmt\ClassLike $stmt, string $className, Scope $scope): ClassReflection
private function createAstClassReflection(Node\Stmt\ClassLike $stmt, string $className, MutatingScope $scope): ClassReflection
{
$analysisFile = $scope->getAnalysisFile();
$nodeToReflection = new NodeToReflection();
$betterReflectionClass = $nodeToReflection->__invoke(
$this->reflector,
$stmt,
new LocatedSource(FileReader::read($scope->getFile()), $className, $scope->getFile()),
new LocatedSource(FileReader::read($analysisFile), $className, $analysisFile),
$scope->getNamespace() !== null ? new Node\Stmt\Namespace_(new Name($scope->getNamespace())) : null,
);
if (!$betterReflectionClass instanceof \PHPStan\BetterReflection\Reflection\ReflectionClass) {
Expand All @@ -2667,7 +2668,7 @@ private function createAstClassReflection(Node\Stmt\ClassLike $stmt, string $cla
null,
null,
null,
sprintf('%s:%d', $scope->getFile(), $stmt->getStartLine()),
sprintf('%s:%d', $analysisFile, $stmt->getStartLine()),
);
}

Expand Down
12 changes: 0 additions & 12 deletions src/Analyser/ResultCache/ResultCacheManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -851,10 +851,6 @@ private function mergeErrors(ResultCache $resultCache, array $freshErrorsByFile)
{
$errorsByFile = $resultCache->getErrors();
foreach ($resultCache->getFilesToAnalyse() as $file) {
if (array_key_exists($file, $this->fileReplacements)) {
unset($errorsByFile[$file]);
$file = $this->fileReplacements[$file];
}
if (!array_key_exists($file, $freshErrorsByFile)) {
unset($errorsByFile[$file]);
continue;
Expand All @@ -873,10 +869,6 @@ private function mergeLocallyIgnoredErrors(ResultCache $resultCache, array $fres
{
$errorsByFile = $resultCache->getLocallyIgnoredErrors();
foreach ($resultCache->getFilesToAnalyse() as $file) {
if (array_key_exists($file, $this->fileReplacements)) {
unset($errorsByFile[$file]);
$file = $this->fileReplacements[$file];
}
if (!array_key_exists($file, $freshLocallyIgnoredErrorsByFile)) {
unset($errorsByFile[$file]);
continue;
Expand All @@ -895,10 +887,6 @@ private function mergeCollectedData(ResultCache $resultCache, array $freshCollec
{
$collectedDataByFile = $resultCache->getCollectedData();
foreach ($resultCache->getFilesToAnalyse() as $file) {
if (array_key_exists($file, $this->fileReplacements)) {
unset($collectedDataByFile[$file]);
$file = $this->fileReplacements[$file];
}
if (!array_key_exists($file, $freshCollectedDataByFile)) {
unset($collectedDataByFile[$file]);
continue;
Expand Down
26 changes: 20 additions & 6 deletions src/Analyser/ScopeContext.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,25 @@ final class ScopeContext

private function __construct(
private string $file,
private string $analysisFile,
private ?ClassReflection $classReflection,
private ?ClassReflection $traitReflection,
)
{
}

/** @api */
public static function create(string $file): self
/**
* @api
* @param string|null $analysisFile the file whose content is actually analysed; differs from $file in editor mode (--tmp-file / --instead-of)
*/
public static function create(string $file, ?string $analysisFile = null): self
{
return new self($file, classReflection: null, traitReflection: null);
return new self($file, $analysisFile ?? $file, classReflection: null, traitReflection: null);
}

public function beginFile(): self
{
return new self($this->file, classReflection: null, traitReflection: null);
return new self($this->file, $this->analysisFile, classReflection: null, traitReflection: null);
}

public function enterClass(ClassReflection $classReflection): self
Expand All @@ -35,7 +39,7 @@ public function enterClass(ClassReflection $classReflection): self
if ($classReflection->isTrait()) {
throw new ShouldNotHappenException();
}
return new self($this->file, $classReflection, traitReflection: null);
return new self($this->file, $this->analysisFile, $classReflection, traitReflection: null);
}

public function enterTrait(ClassReflection $traitReflection): self
Expand All @@ -47,7 +51,7 @@ public function enterTrait(ClassReflection $traitReflection): self
throw new ShouldNotHappenException();
}

return new self($this->file, $this->classReflection, $traitReflection);
return new self($this->file, $this->analysisFile, $this->classReflection, $traitReflection);
}

public function equals(self $otherContext): bool
Expand Down Expand Up @@ -80,6 +84,16 @@ public function getFile(): string
return $this->file;
}

/**
* The file whose content is actually analysed. Equal to getFile() except in
* editor mode (--tmp-file / --instead-of), where getFile() is the real path
* while this is the temp file being read.
*/
public function getAnalysisFile(): string
{
return $this->analysisFile;
}

public function getClassReflection(): ?ClassReflection
{
return $this->classReflection;
Expand Down
2 changes: 2 additions & 0 deletions src/Command/AnalyserRunner.php
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,8 @@ public function runAnalyser(
$postFileCallback,
$debug,
$this->switchTmpFile($allAnalysedFiles, $insteadOfFile, $tmpFile),
$tmpFile,
$insteadOfFile,
);
}

Expand Down
4 changes: 3 additions & 1 deletion src/Parallel/WorkerRunner.php
Original file line number Diff line number Diff line change
Expand Up @@ -163,10 +163,12 @@ private function runWorker(
$processedFiles = [];
foreach ($files as $file) {
try {
$reportedFile = null;
if ($file === $insteadOfFile) {
$reportedFile = $insteadOfFile;
$file = $tmpFile;
}
$fileAnalyserResult = $fileAnalyser->analyseFile($file, $analysedFiles, $ruleRegistry, $collectorRegistry, null);
$fileAnalyserResult = $fileAnalyser->analyseFile($file, $analysedFiles, $ruleRegistry, $collectorRegistry, null, $reportedFile);
$fileErrors = $fileAnalyserResult->getErrors();
$filteredPhpErrors = array_merge($filteredPhpErrors, $fileAnalyserResult->getFilteredPhpErrors());
$allPhpErrors = array_merge($allPhpErrors, $fileAnalyserResult->getAllPhpErrors());
Expand Down
6 changes: 4 additions & 2 deletions src/Reflection/BetterReflection/BetterReflectionProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use Closure;
use Nette\Utils\Strings;
use PhpParser\Node;
use PHPStan\Analyser\EditorModeFileHelper;
use PHPStan\Analyser\Scope;
use PHPStan\BetterReflection\Identifier\Exception\InvalidIdentifierName;
use PHPStan\BetterReflection\NodeCompiler\Exception\UnableToCompileNode;
Expand Down Expand Up @@ -101,6 +102,7 @@ public function __construct(
private FileHelper $fileHelper,
private PhpStormStubsSourceStubber $phpstormStubsSourceStubber,
private AttributeReflectionFactory $attributeReflectionFactory,
private EditorModeFileHelper $editorModeFileHelper,
#[AutowiredParameter(ref: '%universalObjectCratesClasses%')]
private array $universalObjectCratesClasses,
)
Expand Down Expand Up @@ -182,11 +184,11 @@ public function getAnonymousClassReflection(Node\Stmt\Class_ $classNode, Scope $
}

if (!$scope->isInTrait()) {
$scopeFile = $scope->getFile();
$scopeFile = $this->editorModeFileHelper->getAnalysedFile($scope->getFile());
} else {
$scopeFile = $scope->getTraitReflection()->getFileName();
if ($scopeFile === null) {
$scopeFile = $scope->getFile();
$scopeFile = $this->editorModeFileHelper->getAnalysedFile($scope->getFile());
}
}

Expand Down
Loading
Loading