1
0
mirror of https://github.com/danog/psalm.git synced 2025-01-21 21:31:13 +01:00

Fix #731 - report issues in files required by the source

This commit is contained in:
Matt Brown 2018-05-30 12:23:53 -04:00
parent 9227e61097
commit 68dbe509a8
10 changed files with 227 additions and 76 deletions

View File

@ -398,69 +398,16 @@ class ClassChecker extends ClassLikeChecker
$constructor_checker = $method_checker;
}
} elseif ($stmt instanceof PhpParser\Node\Stmt\TraitUse) {
$previous_context_include_location = $class_context->include_location;
foreach ($stmt->traits as $trait) {
$class_context->include_location = new CodeLocation($this, $trait, null, true);
$fq_trait_name = self::getFQCLNFromNameObject(
$trait,
$this->source->getAliases()
);
if (!$codebase->classlikes->hasFullyQualifiedTraitName($fq_trait_name)) {
if (IssueBuffer::accepts(
new UndefinedTrait(
'Trait ' . $fq_trait_name . ' does not exist',
new CodeLocation($this, $trait)
),
array_merge($storage->suppressed_issues, $this->getSuppressedIssues())
)) {
return false;
}
} else {
if (!$codebase->traitHasCorrectCase($fq_trait_name)) {
if (IssueBuffer::accepts(
new UndefinedTrait(
'Trait ' . $fq_trait_name . ' has wrong casing',
new CodeLocation($this, $trait)
),
array_merge($storage->suppressed_issues, $this->getSuppressedIssues())
)) {
return false;
}
continue;
}
$trait_file_checker = $project_checker->getFileCheckerForClassLike($fq_trait_name);
$trait_node = $codebase->classlikes->getTraitNode($fq_trait_name);
$trait_aliases = $codebase->classlikes->getTraitAliases($fq_trait_name);
$trait_checker = new TraitChecker(
$trait_node,
$trait_file_checker,
$fq_trait_name,
$trait_aliases
);
foreach ($trait_node->stmts as $trait_stmt) {
if ($trait_stmt instanceof PhpParser\Node\Stmt\ClassMethod) {
$trait_method_checker = $this->analyzeClassMethod(
$trait_stmt,
$storage,
$trait_checker,
$class_context,
$global_context
);
if ($trait_stmt->name->name === '__construct') {
$constructor_checker = $trait_method_checker;
}
}
}
}
if ($this->analyzeTraitUse(
$stmt,
$project_checker,
$storage,
$class_context,
$global_context,
$constructor_checker
) === false) {
return false;
}
$class_context->include_location = $previous_context_include_location;
} elseif ($stmt instanceof PhpParser\Node\Stmt\Property) {
foreach ($stmt->props as $prop) {
if ($prop->default) {
@ -693,6 +640,96 @@ class ClassChecker extends ClassLikeChecker
}
}
/**
* @return false|null
*/
private function analyzeTraitUse(
PhpParser\Node\Stmt\TraitUse $stmt,
ProjectChecker $project_checker,
ClassLikeStorage $storage,
Context $class_context,
Context $global_context = null,
MethodChecker &$constructor_checker = null
) {
$codebase = $project_checker->codebase;
$previous_context_include_location = $class_context->include_location;
foreach ($stmt->traits as $trait_name) {
$class_context->include_location = new CodeLocation($this, $trait_name, null, true);
$fq_trait_name = self::getFQCLNFromNameObject(
$trait_name,
$this->source->getAliases()
);
if (!$codebase->classlikes->hasFullyQualifiedTraitName($fq_trait_name)) {
if (IssueBuffer::accepts(
new UndefinedTrait(
'Trait ' . $fq_trait_name . ' does not exist',
new CodeLocation($this, $trait_name)
),
array_merge($storage->suppressed_issues, $this->getSuppressedIssues())
)) {
return false;
}
} else {
if (!$codebase->traitHasCorrectCase($fq_trait_name)) {
if (IssueBuffer::accepts(
new UndefinedTrait(
'Trait ' . $fq_trait_name . ' has wrong casing',
new CodeLocation($this, $trait_name)
),
array_merge($storage->suppressed_issues, $this->getSuppressedIssues())
)) {
return false;
}
continue;
}
$trait_file_checker = $project_checker->getFileCheckerForClassLike($fq_trait_name);
$trait_node = $codebase->classlikes->getTraitNode($fq_trait_name);
$trait_aliases = $codebase->classlikes->getTraitAliases($fq_trait_name);
$trait_checker = new TraitChecker(
$trait_node,
$trait_file_checker,
$fq_trait_name,
$trait_aliases
);
foreach ($trait_node->stmts as $trait_stmt) {
if ($trait_stmt instanceof PhpParser\Node\Stmt\ClassMethod) {
$trait_method_checker = $this->analyzeClassMethod(
$trait_stmt,
$storage,
$trait_checker,
$class_context,
$global_context
);
if ($trait_stmt->name->name === '__construct') {
$constructor_checker = $trait_method_checker;
}
} elseif ($trait_stmt instanceof PhpParser\Node\Stmt\TraitUse) {
if ($this->analyzeTraitUse(
$trait_stmt,
$project_checker,
$storage,
$class_context,
$global_context,
$constructor_checker
) === false) {
return false;
}
}
}
}
}
$class_context->include_location = $previous_context_include_location;
}
/**
* @param PhpParser\Node\Stmt\Property $stmt
*

View File

@ -35,7 +35,7 @@ class FileChecker extends SourceChecker implements StatementsSource
/**
* @var array<string, bool>
*/
protected $included_file_paths = [];
protected $required_file_paths = [];
/**
* @var array<int, string>
@ -351,7 +351,7 @@ class FileChecker extends SourceChecker implements StatementsSource
*/
public function addCheckedFilePath($file_path, $file_name)
{
$this->included_file_paths[$file_path] = true;
$this->required_file_paths[$file_path] = true;
$this->checked_file_names[] = $file_name;
$this->checked_file_paths[] = $file_path;
}
@ -390,7 +390,7 @@ class FileChecker extends SourceChecker implements StatementsSource
*/
public function hasAlreadyIncludedFilePath($file_path)
{
return isset($this->included_file_paths[$file_path]);
return isset($this->required_file_paths[$file_path]);
}
/**

View File

@ -385,9 +385,9 @@ class Populator
$dependent_file_paths[$file_path_lc] = true;
$all_included_file_paths = $storage->included_file_paths;
$all_required_file_paths = $storage->required_file_paths;
foreach ($storage->included_file_paths as $included_file_path => $_) {
foreach ($storage->required_file_paths as $included_file_path => $_) {
try {
$included_file_storage = $this->file_storage_provider->get($included_file_path);
} catch (\InvalidArgumentException $e) {
@ -396,10 +396,10 @@ class Populator
$this->populateFileStorage($included_file_storage, $dependent_file_paths);
$all_included_file_paths = $all_included_file_paths + $included_file_storage->included_file_paths;
$all_required_file_paths = $all_required_file_paths + $included_file_storage->required_file_paths;
}
foreach ($all_included_file_paths as $included_file_path => $_) {
foreach ($all_required_file_paths as $included_file_path => $_) {
try {
$included_file_storage = $this->file_storage_provider->get($included_file_path);
} catch (\InvalidArgumentException $e) {
@ -417,7 +417,37 @@ class Populator
);
}
$storage->included_file_paths = $all_included_file_paths;
$storage->required_file_paths = $all_required_file_paths;
foreach ($all_required_file_paths as $required_file_path) {
try {
$required_file_storage = $this->file_storage_provider->get($required_file_path);
} catch (\InvalidArgumentException $e) {
continue;
}
$required_file_storage->required_by_file_paths += [$file_path_lc => $storage->file_path];
}
foreach ($storage->required_classes as $required_classlike) {
try {
$classlike_storage = $this->classlike_storage_provider->get($required_classlike);
} catch (\InvalidArgumentException $e) {
continue;
}
if (!$classlike_storage->location) {
continue;
}
try {
$required_file_storage = $this->file_storage_provider->get($classlike_storage->location->file_path);
} catch (\InvalidArgumentException $e) {
continue;
}
$required_file_storage->required_by_file_paths += [$file_path_lc => $storage->file_path];
}
$storage->populated = true;
}

View File

@ -316,8 +316,8 @@ class Scanner
$this->file_storage_provider->cache->writeToCache($file_storage, $file_contents);
}
} else {
foreach ($file_storage->included_file_paths as $include_file_path) {
$this->addFileToShallowScan($include_file_path);
foreach ($file_storage->required_file_paths as $required_file_path) {
$this->addFileToShallowScan($required_file_path);
}
foreach ($file_storage->classlikes_in_file as $fq_classlike_name) {

View File

@ -791,7 +791,25 @@ class Config
if ($this->hide_external_errors) {
$codebase = ProjectChecker::getInstance()->codebase;
if (!$codebase->analyzer->canReportIssues($file_path)) {
$dependent_files = [strtolower($file_path) => $file_path];
try {
$file_storage = $codebase->file_storage_provider->get($file_path);
$dependent_files += $file_storage->required_by_file_paths;
} catch (\InvalidArgumentException $e) {
// do nothing
}
$any_file_path_matched = false;
foreach ($dependent_files as $file_path) {
if ($codebase->analyzer->canReportIssues($file_path)) {
$any_file_path_matched = true;
break;
}
}
if (!$any_file_path_matched) {
return false;
}
}

View File

@ -48,7 +48,10 @@ class FileStorage
public $declaring_constants = [];
/** @var array<string, string> */
public $included_file_paths = [];
public $required_file_paths = [];
/** @var array<string, string> */
public $required_by_file_paths = [];
/** @var bool */
public $populated = false;

View File

@ -1632,7 +1632,7 @@ class DependencyFinderVisitor extends PhpParser\NodeVisitorAbstract implements P
$this->codebase->scanner->addFileToShallowScan($path_to_file);
}
$this->file_storage->included_file_paths[strtolower($path_to_file)] = $path_to_file;
$this->file_storage->required_file_paths[strtolower($path_to_file)] = $path_to_file;
return;
}

View File

@ -440,6 +440,52 @@ class IncludeTest extends TestCase
'error_message' => 'UndefinedGlobalVariable',
'hide_external_errors' => false
],
'invalidTraitFunctionReturnInUncheckedFile' => [
'files' => [
getcwd() . DIRECTORY_SEPARATOR . 'file2.php' => '<?php
require("file1.php");
class B {
use A;
}',
getcwd() . DIRECTORY_SEPARATOR . 'file1.php' => '<?php
trait A{
public function fooFoo(): string {
return 5;
}
}',
],
'files_to_check' => [
getcwd() . DIRECTORY_SEPARATOR . 'file2.php',
],
'error_message' => 'InvalidReturnType',
],
'invalidDoubleNestedTraitFunctionReturnInUncheckedFile' => [
'files' => [
getcwd() . DIRECTORY_SEPARATOR . 'file3.php' => '<?php
require("file2.php");
class C {
use B;
}',
getcwd() . DIRECTORY_SEPARATOR . 'file2.php' => '<?php
require("file1.php");
trait B {
use A;
}',
getcwd() . DIRECTORY_SEPARATOR . 'file1.php' => '<?php
trait A{
public function fooFoo(): string {
return 5;
}
}',
],
'files_to_check' => [
getcwd() . DIRECTORY_SEPARATOR . 'file3.php',
],
'error_message' => 'InvalidReturnType',
],
];
}
}

View File

@ -460,7 +460,7 @@ class PropertyTypeTest extends TestCase
}
class A {
use T;
use T;
}',
],
'abstractClassWithNoConstructor' => [

View File

@ -695,6 +695,23 @@ class TraitTest extends TestCase
}',
'error_message' => 'MissingPropertyType',
],
'nestedTraitWithBadReturnType' => [
'<?php
trait A {
public function foo() : string {
return 5;
}
}
trait B {
use A;
}
class C {
use B;
}',
'error_message' => 'InvalidReturnType',
],
];
}
}