Skip to content

Commit

Permalink
Bleeding edge - check nonexistent classes in LocalTypeAliasesCheck
Browse files Browse the repository at this point in the history
  • Loading branch information
ondrejmirtes committed Aug 24, 2024
1 parent ce7ffaf commit 2485b2e
Show file tree
Hide file tree
Showing 7 changed files with 76 additions and 3 deletions.
1 change: 1 addition & 0 deletions conf/config.neon
Original file line number Diff line number Diff line change
Expand Up @@ -914,6 +914,7 @@ services:
arguments:
globalTypeAliases: %typeAliases%
checkMissingTypehints: %checkMissingTypehints%
checkClassCaseSensitivity: %checkClassCaseSensitivity%
absentTypeChecks: %featureToggles.absentTypeChecks%

-
Expand Down
27 changes: 26 additions & 1 deletion src/Rules/Classes/LocalTypeAliasesCheck.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,14 @@

namespace PHPStan\Rules\Classes;

use PhpParser\Node\Stmt\ClassLike;
use PHPStan\Analyser\NameScope;
use PHPStan\PhpDoc\TypeNodeResolver;
use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode;
use PHPStan\Reflection\ClassReflection;
use PHPStan\Reflection\ReflectionProvider;
use PHPStan\Rules\ClassNameCheck;
use PHPStan\Rules\ClassNameNodePair;
use PHPStan\Rules\IdentifierRuleError;
use PHPStan\Rules\MissingTypehintCheck;
use PHPStan\Rules\RuleErrorBuilder;
Expand All @@ -31,7 +34,9 @@ public function __construct(
private ReflectionProvider $reflectionProvider,
private TypeNodeResolver $typeNodeResolver,
private MissingTypehintCheck $missingTypehintCheck,
private ClassNameCheck $classCheck,
private bool $checkMissingTypehints,
private bool $checkClassCaseSensitivity,
private bool $absentTypeChecks,
)
{
Expand All @@ -40,7 +45,7 @@ public function __construct(
/**
* @return list<IdentifierRuleError>
*/
public function check(ClassReflection $reflection): array
public function check(ClassReflection $reflection, ClassLike $node): array
{
$phpDoc = $reflection->getResolvedPhpDoc();
if ($phpDoc === null) {
Expand Down Expand Up @@ -214,6 +219,26 @@ public function check(ClassReflection $reflection): array
))->identifier('missingType.callable')->build();
}
}

foreach ($resolvedType->getReferencedClasses() as $class) {
if (!$this->reflectionProvider->hasClass($class)) {
$errors[] = RuleErrorBuilder::message(sprintf('Type alias %s contains unknown class %s.', $aliasName, $class))
->identifier('class.notFound')
->discoveringSymbolsTip()
->build();
} elseif ($this->reflectionProvider->getClass($class)->isTrait()) {
$errors[] = RuleErrorBuilder::message(sprintf('Type alias %s contains invalid type %s.', $aliasName, $class))
->identifier('typeAlias.trait')
->build();
} else {
$errors = array_merge(
$errors,
$this->classCheck->checkClassNames([
new ClassNameNodePair($class, $node),
], $this->checkClassCaseSensitivity),
);
}
}
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/Rules/Classes/LocalTypeAliasesRule.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public function getNodeType(): string

public function processNode(Node $node, Scope $scope): array
{
return $this->check->check($node->getClassReflection());
return $this->check->check($node->getClassReflection(), $node->getOriginalNode());
}

}
2 changes: 1 addition & 1 deletion src/Rules/Classes/LocalTypeTraitAliasesRule.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public function processNode(Node $node, Scope $scope): array
return [];
}

return $this->check->check($this->reflectionProvider->getClass($traitName->toString()));
return $this->check->check($this->reflectionProvider->getClass($traitName->toString()), $node);
}

}
23 changes: 23 additions & 0 deletions tests/PHPStan/Rules/Classes/LocalTypeAliasesRuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
namespace PHPStan\Rules\Classes;

use PHPStan\PhpDoc\TypeNodeResolver;
use PHPStan\Rules\ClassCaseSensitivityCheck;
use PHPStan\Rules\ClassForbiddenNameCheck;
use PHPStan\Rules\ClassNameCheck;
use PHPStan\Rules\MissingTypehintCheck;
use PHPStan\Rules\Rule;
use PHPStan\Testing\RuleTestCase;
Expand All @@ -16,12 +19,19 @@ class LocalTypeAliasesRuleTest extends RuleTestCase

protected function getRule(): Rule
{
$reflectionProvider = $this->createReflectionProvider();

return new LocalTypeAliasesRule(
new LocalTypeAliasesCheck(
['GlobalTypeAlias' => 'int|string'],
$this->createReflectionProvider(),
self::getContainer()->getByType(TypeNodeResolver::class),
new MissingTypehintCheck(true, true, true, true, []),
new ClassNameCheck(
new ClassCaseSensitivityCheck($reflectionProvider, true),
new ClassForbiddenNameCheck(self::getContainer()),
),
true,
true,
true,
),
Expand Down Expand Up @@ -108,6 +118,19 @@ public function testRule(): void
'Class LocalTypeAliases\MissingTypehints has type alias NoCallable with no signature specified for callable.',
77,
],
[
'Type alias A contains unknown class LocalTypeAliases\Nonexistent.',
87,
'Learn more at https://phpstan.org/user-guide/discovering-symbols',
],
[
'Type alias B contains invalid type LocalTypeTraitAliases\Foo.',
87,
],
[
'Class LocalTypeAliases\Foo referenced with incorrect case: LocalTypeAliases\fOO.',
87,
],
]);
}

Expand Down
14 changes: 14 additions & 0 deletions tests/PHPStan/Rules/Classes/LocalTypeTraitAliasesRuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
namespace PHPStan\Rules\Classes;

use PHPStan\PhpDoc\TypeNodeResolver;
use PHPStan\Rules\ClassCaseSensitivityCheck;
use PHPStan\Rules\ClassForbiddenNameCheck;
use PHPStan\Rules\ClassNameCheck;
use PHPStan\Rules\MissingTypehintCheck;
use PHPStan\Rules\Rule;
use PHPStan\Testing\RuleTestCase;

Expand All @@ -14,11 +18,21 @@ class LocalTypeTraitAliasesRuleTest extends RuleTestCase

protected function getRule(): Rule
{
$reflectionProvider = $this->createReflectionProvider();

return new LocalTypeTraitAliasesRule(
new LocalTypeAliasesCheck(
['GlobalTypeAlias' => 'int|string'],
$this->createReflectionProvider(),
self::getContainer()->getByType(TypeNodeResolver::class),
new MissingTypehintCheck(true, true, true, true, []),
new ClassNameCheck(
new ClassCaseSensitivityCheck($reflectionProvider, true),
new ClassForbiddenNameCheck(self::getContainer()),
),
true,
true,
true,
),
$this->createReflectionProvider(),
);
Expand Down
10 changes: 10 additions & 0 deletions tests/PHPStan/Rules/Classes/data/local-type-aliases.php
Original file line number Diff line number Diff line change
Expand Up @@ -78,3 +78,13 @@ class MissingTypehints
{

}

/**
* @phpstan-type A = Nonexistent
* @phpstan-type B = \LocalTypeTraitAliases\Foo
* @phpstan-type C = fOO
*/
class NonexistentClasses
{

}

0 comments on commit 2485b2e

Please sign in to comment.