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

Add separate issue for undefined classes in docblocks

This commit is contained in:
Brown 2019-05-15 18:41:26 -04:00
parent 02498a3d74
commit 4a434d9a2f
16 changed files with 128 additions and 29 deletions

View File

@ -313,6 +313,7 @@
<xs:element name="UncaughtThrowInGlobalScope" type="IssueHandlerType" minOccurs="0" />
<xs:element name="UndefinedClass" type="ClassIssueHandlerType" minOccurs="0" />
<xs:element name="UndefinedConstant" type="IssueHandlerType" minOccurs="0" />
<xs:element name="UndefinedDocblockClass" type="ClassIssueHandlerType" minOccurs="0" />
<xs:element name="UndefinedFunction" type="FunctionIssueHandlerType" minOccurs="0" />
<xs:element name="UndefinedInterface" type="ClassIssueHandlerType" minOccurs="0" />
<xs:element name="UndefinedInterfaceMethod" type="MethodIssueHandlerType" minOccurs="0" />

View File

@ -2084,6 +2084,17 @@ Emitted when referencing a constant that doesnt exist
echo FOO_BAR;
```
### UndefinedDocblockClass
Emitted when referencing a class that doesnt exist from a docblock
```php
/**
* @param DoesNotExist $a
*/
function foo($a) : void {}
```
### UndefinedFunction
Emitted when referencing a function that doesn't exist

View File

@ -1252,6 +1252,10 @@ class Config
return 'MoreSpecificImplementedParamType';
}
if ($issue_type === 'UndefinedDocblockClass') {
return 'UndefinedClass';
}
if ($issue_type === 'MixedArgumentTypeCoercion'
|| $issue_type === 'MixedPropertyTypeCoercion'
|| $issue_type === 'MixedReturnTypeCoercion'

View File

@ -12,6 +12,7 @@ use Psalm\Issue\InvalidClass;
use Psalm\Issue\MissingDependency;
use Psalm\Issue\ReservedWord;
use Psalm\Issue\UndefinedClass;
use Psalm\Issue\UndefinedDocblockClass;
use Psalm\IssueBuffer;
use Psalm\StatementsSource;
use Psalm\Storage\ClassLikeStorage;
@ -183,9 +184,10 @@ abstract class ClassLikeAnalyzer extends SourceAnalyzer implements StatementsSou
$fq_class_name,
CodeLocation $code_location,
array $suppressed_issues,
$inferred = true,
bool $inferred = true,
bool $allow_trait = false,
bool $allow_interface = true
bool $allow_interface = true,
bool $from_docblock = false
) {
$codebase = $statements_source->getCodebase();
if (empty($fq_class_name)) {
@ -242,15 +244,28 @@ abstract class ClassLikeAnalyzer extends SourceAnalyzer implements StatementsSou
if (!$class_exists && !$interface_exists) {
if (!$allow_trait || !$codebase->classlikes->traitExists($fq_class_name, $code_location)) {
if (IssueBuffer::accepts(
new UndefinedClass(
'Class or interface ' . $fq_class_name . ' does not exist',
$code_location,
$fq_class_name
),
$suppressed_issues
)) {
return false;
if ($from_docblock) {
if (IssueBuffer::accepts(
new UndefinedDocblockClass(
'Docblock-defined class or interface ' . $fq_class_name . ' does not exist',
$code_location,
$fq_class_name
),
$suppressed_issues
)) {
return false;
}
} else {
if (IssueBuffer::accepts(
new UndefinedClass(
'Class or interface ' . $fq_class_name . ' does not exist',
$code_location,
$fq_class_name
),
$suppressed_issues
)) {
return false;
}
}
}

View File

@ -554,12 +554,17 @@ class MethodCallAnalyzer extends \Psalm\Internal\Analyzer\Statements\Expression\
$statements_analyzer,
$fq_class_name,
new CodeLocation($source, $stmt->var),
$statements_analyzer->getSuppressedIssues()
$statements_analyzer->getSuppressedIssues(),
true,
false,
true,
$lhs_type_part->from_docblock
);
}
if (!$does_class_exist) {
return $does_class_exist;
$non_existent_class_method_ids[] = $fq_class_name . '::*';
return false;
}
if (!$stmt->name instanceof PhpParser\Node\Identifier) {
@ -782,7 +787,11 @@ class MethodCallAnalyzer extends \Psalm\Internal\Analyzer\Statements\Expression\
$statements_analyzer,
$fq_class_name,
new CodeLocation($source, $stmt->var),
$statements_analyzer->getSuppressedIssues()
$statements_analyzer->getSuppressedIssues(),
false,
false,
true,
$intersection_type->from_docblock
);
if (!$does_class_exist) {

View File

@ -2905,6 +2905,18 @@ class CallAnalyzer
foreach ($changed_vars as $changed_var) {
if (isset($op_vars_in_scope[$changed_var])) {
$op_vars_in_scope[$changed_var]->from_docblock = true;
foreach ($op_vars_in_scope[$changed_var]->getTypes() as $changed_atomic_type) {
$changed_atomic_type->from_docblock = true;
if ($changed_atomic_type instanceof Type\Atomic\TNamedObject
&& $changed_atomic_type->extra_types
) {
foreach ($changed_atomic_type->extra_types as $extra_type) {
$extra_type->from_docblock = true;
}
}
}
}
}

View File

@ -111,7 +111,7 @@ class LanguageServer extends AdvancedJsonRpc\Dispatcher
// Invoke the method handler to get a result
/**
* @var Promise
* @psalm-suppress UndefinedClass
* @psalm-suppress UndefinedDocblockClass
*/
$dispatched = $this->dispatch($msg->body);
$result = yield $dispatched;

View File

@ -1050,6 +1050,8 @@ class ReflectorVisitor extends PhpParser\NodeVisitorAbstract implements PhpParse
}
}
$extended_union_type->setFromDocblock();
foreach ($extended_union_type->getTypes() as $atomic_type) {
if (!$atomic_type instanceof Type\Atomic\TGenericObject) {
if (IssueBuffer::accepts(
@ -1134,6 +1136,8 @@ class ReflectorVisitor extends PhpParser\NodeVisitorAbstract implements PhpParse
}
}
$implemented_union_type->setFromDocblock();
foreach ($implemented_union_type->getTypes() as $atomic_type) {
if (!$atomic_type instanceof Type\Atomic\TGenericObject) {
if (IssueBuffer::accepts(
@ -1216,6 +1220,8 @@ class ReflectorVisitor extends PhpParser\NodeVisitorAbstract implements PhpParse
}
}
$used_union_type->setFromDocblock();
foreach ($used_union_type->getTypes() as $atomic_type) {
if (!$atomic_type instanceof Type\Atomic\TGenericObject) {
if (IssueBuffer::accepts(

View File

@ -0,0 +1,6 @@
<?php
namespace Psalm\Issue;
class UndefinedDocblockClass extends ClassIssue
{
}

View File

@ -294,7 +294,10 @@ abstract class Atomic
$this->value,
$code_location,
$suppressed_issues,
$inferred
$inferred,
false,
true,
$this->from_docblock
) === false
) {
return false;
@ -312,7 +315,10 @@ abstract class Atomic
$extra_type->value,
$code_location,
$suppressed_issues,
$inferred
$inferred,
false,
true,
$this->from_docblock
) === false
) {
return false;
@ -327,7 +333,10 @@ abstract class Atomic
$this->value,
$code_location,
$suppressed_issues,
$inferred
$inferred,
false,
true,
$this->from_docblock
) === false
) {
return false;
@ -351,7 +360,10 @@ abstract class Atomic
$this->as,
$code_location,
$suppressed_issues,
$inferred
$inferred,
false,
true,
$this->from_docblock
) === false
) {
return false;
@ -403,7 +415,10 @@ abstract class Atomic
$fq_classlike_name,
$code_location,
$suppressed_issues,
$inferred
$inferred,
false,
true,
$this->from_docblock
) === false
) {
return false;

View File

@ -1177,6 +1177,26 @@ class AnnotationTest extends TestCase
}',
'error_message' => 'UndefinedClass',
],
'undefinedDocblockClassCall' => [
'<?php
class B {
/**
* @return A
* @psalm-suppress UndefinedDocblockClass
* @psalm-suppress InvalidReturnStatement
* @psalm-suppress InvalidReturnType
*/
public function foo() {
return new stdClass();
}
public function bar() {
$this->foo()->bar();
}
}
',
'error_message' => 'UndefinedDocblockClass',
],
'preventBadObjectLikeFormat' => [
'<?php
/**
@ -1321,7 +1341,7 @@ class AnnotationTest extends TestCase
'<?php
/** @var Foo */
$a = $_GET["foo"];',
'error_message' => 'UndefinedClass',
'error_message' => 'UndefinedDocblockClass',
],
'badPsalmType' => [
'<?php

View File

@ -854,7 +854,7 @@ class AssertTest extends TestCase
assertInstanceOf($bar, Bar::class);
$bar->sayHello();',
'error_message' => 'UndefinedClass',
'error_message' => 'UndefinedDocblockClass',
],
'detectRedundantCondition' => [
'<?php

View File

@ -165,7 +165,7 @@ class EnumTest extends TestCase
/** @psalm-param "foo"|"bar"|C::A|C::B $s */
function foo($s) : void {}',
'error_message' => 'UndefinedClass',
'error_message' => 'UndefinedDocblockClass',
],
'selfClassConstBadValue' => [
'<?php
@ -221,7 +221,7 @@ class EnumTest extends TestCase
{
return 5;
}',
'error_message' => 'UndefinedClass',
'error_message' => 'UndefinedDocblockClass',
],
'nonExistentClassConstant' => [
'<?php

View File

@ -2106,7 +2106,7 @@ class PropertyTypeTest extends TestCase
/** @var B */
public $foo;
}',
'error_message' => 'UndefinedClass',
'error_message' => 'UndefinedDocblockClass',
],
'abstractClassWithNoConstructorButChild' => [
'<?php

View File

@ -2078,7 +2078,7 @@ class TemplateExtendsTest extends TestCase
* @template-extends A<Z>
*/
class B extends A {}',
'error_message' => 'UndefinedClass',
'error_message' => 'UndefinedDocblockClass',
],
'badTemplateExtendsInt' => [
'<?php
@ -2161,7 +2161,7 @@ class TemplateExtendsTest extends TestCase
* @template-implements I<Z>
*/
class B implements I {}',
'error_message' => 'UndefinedClass',
'error_message' => 'UndefinedDocblockClass',
],
'badTemplateImplementsInt' => [
'<?php
@ -2238,7 +2238,7 @@ class TemplateExtendsTest extends TestCase
*/
use T;
}',
'error_message' => 'UndefinedClass',
'error_message' => 'UndefinedDocblockClass',
],
'badTemplateUseBadFormat' => [
'<?php

View File

@ -1047,7 +1047,7 @@ class TraitTest extends TestCase
/** @var T|null */
public $hm;
}',
'error_message' => 'UndefinedClass',
'error_message' => 'UndefinedDocblockClass',
],
];
}