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:
parent
02498a3d74
commit
4a434d9a2f
@ -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" />
|
||||
|
@ -2084,6 +2084,17 @@ Emitted when referencing a constant that doesn’t exist
|
||||
echo FOO_BAR;
|
||||
```
|
||||
|
||||
### UndefinedDocblockClass
|
||||
|
||||
Emitted when referencing a class that doesn’t exist from a docblock
|
||||
|
||||
```php
|
||||
/**
|
||||
* @param DoesNotExist $a
|
||||
*/
|
||||
function foo($a) : void {}
|
||||
```
|
||||
|
||||
### UndefinedFunction
|
||||
|
||||
Emitted when referencing a function that doesn't exist
|
||||
|
@ -1252,6 +1252,10 @@ class Config
|
||||
return 'MoreSpecificImplementedParamType';
|
||||
}
|
||||
|
||||
if ($issue_type === 'UndefinedDocblockClass') {
|
||||
return 'UndefinedClass';
|
||||
}
|
||||
|
||||
if ($issue_type === 'MixedArgumentTypeCoercion'
|
||||
|| $issue_type === 'MixedPropertyTypeCoercion'
|
||||
|| $issue_type === 'MixedReturnTypeCoercion'
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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) {
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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(
|
||||
|
6
src/Psalm/Issue/UndefinedDocblockClass.php
Normal file
6
src/Psalm/Issue/UndefinedDocblockClass.php
Normal file
@ -0,0 +1,6 @@
|
||||
<?php
|
||||
namespace Psalm\Issue;
|
||||
|
||||
class UndefinedDocblockClass extends ClassIssue
|
||||
{
|
||||
}
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -854,7 +854,7 @@ class AssertTest extends TestCase
|
||||
assertInstanceOf($bar, Bar::class);
|
||||
|
||||
$bar->sayHello();',
|
||||
'error_message' => 'UndefinedClass',
|
||||
'error_message' => 'UndefinedDocblockClass',
|
||||
],
|
||||
'detectRedundantCondition' => [
|
||||
'<?php
|
||||
|
@ -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
|
||||
|
@ -2106,7 +2106,7 @@ class PropertyTypeTest extends TestCase
|
||||
/** @var B */
|
||||
public $foo;
|
||||
}',
|
||||
'error_message' => 'UndefinedClass',
|
||||
'error_message' => 'UndefinedDocblockClass',
|
||||
],
|
||||
'abstractClassWithNoConstructorButChild' => [
|
||||
'<?php
|
||||
|
@ -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
|
||||
|
@ -1047,7 +1047,7 @@ class TraitTest extends TestCase
|
||||
/** @var T|null */
|
||||
public $hm;
|
||||
}',
|
||||
'error_message' => 'UndefinedClass',
|
||||
'error_message' => 'UndefinedDocblockClass',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user