Skip to content

Commit

Permalink
Bleeding edge - MissingMethodSelfOutTypeRule
Browse files Browse the repository at this point in the history
  • Loading branch information
ondrejmirtes committed Aug 23, 2024
1 parent 9ebc315 commit 892b319
Show file tree
Hide file tree
Showing 5 changed files with 171 additions and 0 deletions.
7 changes: 7 additions & 0 deletions conf/config.level6.neon
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ rules:
- PHPStan\Rules\Methods\MissingMethodReturnTypehintRule
- PHPStan\Rules\Properties\MissingPropertyTypehintRule

conditionalTags:
PHPStan\Rules\Methods\MissingMethodSelfOutTypeRule:
phpstan.rules.rule: %featureToggles.absentTypeChecks%

services:
-
class: PHPStan\Rules\Functions\MissingFunctionParameterTypehintRule
Expand All @@ -27,3 +31,6 @@ services:
paramOut: %featureToggles.paramOutType%
tags:
- phpstan.rules.rule

-
class: PHPStan\Rules\Methods\MissingMethodSelfOutTypeRule
5 changes: 5 additions & 0 deletions src/PhpDoc/StubValidator.php
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
use PHPStan\Rules\Methods\MethodSignatureRule;
use PHPStan\Rules\Methods\MissingMethodParameterTypehintRule;
use PHPStan\Rules\Methods\MissingMethodReturnTypehintRule;
use PHPStan\Rules\Methods\MissingMethodSelfOutTypeRule;
use PHPStan\Rules\Methods\OverridingMethodRule;
use PHPStan\Rules\MissingTypehintCheck;
use PHPStan\Rules\PhpDoc\GenericCallableRuleHelper;
Expand Down Expand Up @@ -228,6 +229,10 @@ private function getRuleRegistry(Container $container): RuleRegistry
);
}

if ((bool) $container->getParameter('featureToggles')['absentTypeChecks']) {
$rules[] = new MissingMethodSelfOutTypeRule($missingTypehintCheck);
}

return new DirectRuleRegistry($rules);
}

Expand Down
85 changes: 85 additions & 0 deletions src/Rules/Methods/MissingMethodSelfOutTypeRule.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
<?php declare(strict_types = 1);

namespace PHPStan\Rules\Methods;

use PhpParser\Node;
use PHPStan\Analyser\Scope;
use PHPStan\Node\InClassMethodNode;
use PHPStan\Rules\MissingTypehintCheck;
use PHPStan\Rules\Rule;
use PHPStan\Rules\RuleErrorBuilder;
use PHPStan\Type\VerbosityLevel;
use function implode;
use function sprintf;

/**
* @implements Rule<InClassMethodNode>
*/
final class MissingMethodSelfOutTypeRule implements Rule
{

public function __construct(
private MissingTypehintCheck $missingTypehintCheck,
)
{
}

public function getNodeType(): string
{
return InClassMethodNode::class;
}

public function processNode(Node $node, Scope $scope): array
{
$methodReflection = $node->getMethodReflection();
$selfOutType = $methodReflection->getSelfOutType();

if ($selfOutType === null) {
return [];
}

$classReflection = $methodReflection->getDeclaringClass();
$phpDocTagMessage = 'PHPDoc tag @phpstan-self-out';

$messages = [];
foreach ($this->missingTypehintCheck->getIterableTypesWithMissingValueTypehint($selfOutType) as $iterableType) {
$iterableTypeDescription = $iterableType->describe(VerbosityLevel::typeOnly());
$messages[] = RuleErrorBuilder::message(sprintf(
'Method %s::%s() has %s with no value type specified in iterable type %s.',
$classReflection->getDisplayName(),
$methodReflection->getName(),
$phpDocTagMessage,
$iterableTypeDescription,
))
->tip(MissingTypehintCheck::MISSING_ITERABLE_VALUE_TYPE_TIP)
->identifier('missingType.iterableValue')
->build();
}

foreach ($this->missingTypehintCheck->getNonGenericObjectTypesWithGenericClass($selfOutType) as [$name, $genericTypeNames]) {
$messages[] = RuleErrorBuilder::message(sprintf(
'Method %s::%s() has %s with generic %s but does not specify its types: %s',
$classReflection->getDisplayName(),
$methodReflection->getName(),
$phpDocTagMessage,
$name,
implode(', ', $genericTypeNames),
))
->identifier('missingType.generics')
->build();
}

foreach ($this->missingTypehintCheck->getCallablesWithMissingSignature($selfOutType) as $callableType) {
$messages[] = RuleErrorBuilder::message(sprintf(
'Method %s::%s() has %s with no signature specified for %s.',
$classReflection->getDisplayName(),
$methodReflection->getName(),
$phpDocTagMessage,
$callableType->describe(VerbosityLevel::typeOnly()),
))->identifier('missingType.callable')->build();
}

return $messages;
}

}
39 changes: 39 additions & 0 deletions tests/PHPStan/Rules/Methods/MissingMethodSelfOutTypeRuleTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?php declare(strict_types = 1);

namespace PHPStan\Rules\Methods;

use PHPStan\Rules\MissingTypehintCheck;
use PHPStan\Rules\Rule as TRule;
use PHPStan\Testing\RuleTestCase;

/**
* @extends RuleTestCase<MissingMethodSelfOutTypeRule>
*/
class MissingMethodSelfOutTypeRuleTest extends RuleTestCase
{

protected function getRule(): TRule
{
return new MissingMethodSelfOutTypeRule(new MissingTypehintCheck(true, true, true, true, []));
}

public function testRule(): void
{
$this->analyse([__DIR__ . '/data/missing-method-self-out-type.php'], [
[
'Method MissingMethodSelfOutType\Foo::doFoo() has PHPDoc tag @phpstan-self-out with no value type specified in iterable type array.',
14,
'See: https://phpstan.org/blog/solving-phpstan-no-value-type-specified-in-iterable-type',
],
[
'Method MissingMethodSelfOutType\Foo::doFoo2() has PHPDoc tag @phpstan-self-out with generic class MissingMethodSelfOutType\Foo but does not specify its types: T',
22,
],
[
'Method MissingMethodSelfOutType\Foo::doFoo3() has PHPDoc tag @phpstan-self-out with no signature specified for callable.',
30,
],
]);
}

}
35 changes: 35 additions & 0 deletions tests/PHPStan/Rules/Methods/data/missing-method-self-out-type.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php

namespace MissingMethodSelfOutType;

/**
* @template T
*/
class Foo
{

/**
* @phpstan-self-out self<array>
*/
public function doFoo(): void
{

}

/**
* @phpstan-self-out self
*/
public function doFoo2(): void
{

}

/**
* @phpstan-self-out Foo<int>&callable
*/
public function doFoo3(): void
{

}

}

0 comments on commit 892b319

Please sign in to comment.