Skip to content

Commit 7abcc51

Browse files
stofondrejmirtes
authored andcommitted
Add support for a not-deprecated annotation
This annotation allows disabling the inheritance of method deprecations when a child class does not want to deprecate its method even though it corresponds to a deprecated parent method.
1 parent a7d1be1 commit 7abcc51

File tree

4 files changed

+68
-3
lines changed

4 files changed

+68
-3
lines changed

Diff for: src/PhpDoc/PhpDocNodeResolver.php

+7
Original file line numberDiff line numberDiff line change
@@ -517,6 +517,13 @@ public function resolveIsDeprecated(PhpDocNode $phpDocNode): bool
517517
return count($deprecatedTags) > 0;
518518
}
519519

520+
public function resolveIsNotDeprecated(PhpDocNode $phpDocNode): bool
521+
{
522+
$notDeprecatedTags = $phpDocNode->getTagsByName('@not-deprecated');
523+
524+
return count($notDeprecatedTags) > 0;
525+
}
526+
520527
public function resolveIsInternal(PhpDocNode $phpDocNode): bool
521528
{
522529
$internalTags = $phpDocNode->getTagsByName('@internal');

Diff for: src/PhpDoc/ResolvedPhpDocBlock.php

+26-3
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,8 @@ class ResolvedPhpDocBlock
103103

104104
private ?bool $isDeprecated = null;
105105

106+
private ?bool $isNotDeprecated = null;
107+
106108
private ?bool $isInternal = null;
107109

108110
private ?bool $isFinal = null;
@@ -177,6 +179,7 @@ public static function createEmpty(): self
177179
$self->selfOutTypeTag = null;
178180
$self->deprecatedTag = null;
179181
$self->isDeprecated = false;
182+
$self->isNotDeprecated = false;
180183
$self->isInternal = false;
181184
$self->isFinal = false;
182185
$self->isPure = null;
@@ -229,8 +232,9 @@ public function merge(array $parents, array $parentPhpDocBlocks): self
229232
$result->typeAliasImportTags = $this->getTypeAliasImportTags();
230233
$result->assertTags = self::mergeAssertTags($this->getAssertTags(), $parents, $parentPhpDocBlocks);
231234
$result->selfOutTypeTag = self::mergeSelfOutTypeTags($this->getSelfOutTag(), $parents);
232-
$result->deprecatedTag = self::mergeDeprecatedTags($this->getDeprecatedTag(), $parents);
235+
$result->deprecatedTag = self::mergeDeprecatedTags($this->getDeprecatedTag(), $this->isNotDeprecated(), $parents);
233236
$result->isDeprecated = $result->deprecatedTag !== null;
237+
$result->isNotDeprecated = $this->isNotDeprecated();
234238
$result->isInternal = $this->isInternal();
235239
$result->isFinal = $this->isFinal();
236240
$result->isPure = $this->isPure();
@@ -324,6 +328,7 @@ public function changeParameterNamesByMapping(array $parameterNameMapping): self
324328
$self->selfOutTypeTag = $this->selfOutTypeTag;
325329
$self->deprecatedTag = $this->deprecatedTag;
326330
$self->isDeprecated = $this->isDeprecated;
331+
$self->isNotDeprecated = $this->isNotDeprecated;
327332
$self->isInternal = $this->isInternal;
328333
$self->isFinal = $this->isFinal;
329334
$self->isPure = $this->isPure;
@@ -599,6 +604,19 @@ public function isDeprecated(): bool
599604
return $this->isDeprecated;
600605
}
601606

607+
/**
608+
* @internal
609+
*/
610+
public function isNotDeprecated(): bool
611+
{
612+
if ($this->isNotDeprecated === null) {
613+
$this->isNotDeprecated = $this->phpDocNodeResolver->resolveIsNotDeprecated(
614+
$this->phpDocNode,
615+
);
616+
}
617+
return $this->isNotDeprecated;
618+
}
619+
602620
public function isInternal(): bool
603621
{
604622
if ($this->isInternal === null) {
@@ -871,14 +889,19 @@ private static function mergeSelfOutTypeTags(?SelfOutTypeTag $selfOutTypeTag, ar
871889
/**
872890
* @param array<int, self> $parents
873891
*/
874-
private static function mergeDeprecatedTags(?DeprecatedTag $deprecatedTag, array $parents): ?DeprecatedTag
892+
private static function mergeDeprecatedTags(?DeprecatedTag $deprecatedTag, bool $hasNotDeprecatedTag, array $parents): ?DeprecatedTag
875893
{
876894
if ($deprecatedTag !== null) {
877895
return $deprecatedTag;
878896
}
897+
898+
if ($hasNotDeprecatedTag) {
899+
return null;
900+
}
901+
879902
foreach ($parents as $parent) {
880903
$result = $parent->getDeprecatedTag();
881-
if ($result === null) {
904+
if ($result === null && !$parent->isNotDeprecated()) {
882905
continue;
883906
}
884907
return $result;

Diff for: tests/PHPStan/Reflection/Annotations/DeprecatedAnnotationsTest.php

+12
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,14 @@
22

33
namespace PHPStan\Reflection\Annotations;
44

5+
use DeprecatedAnnotations\Baz;
6+
use DeprecatedAnnotations\BazInterface;
57
use DeprecatedAnnotations\DeprecatedBar;
68
use DeprecatedAnnotations\DeprecatedFoo;
79
use DeprecatedAnnotations\DeprecatedWithMultipleTags;
810
use DeprecatedAnnotations\Foo;
911
use DeprecatedAnnotations\FooInterface;
12+
use DeprecatedAnnotations\SubBazInterface;
1013
use PhpParser\Node\Name;
1114
use PHPStan\Analyser\Scope;
1215
use PHPStan\Testing\PHPStanTestCase;
@@ -141,4 +144,13 @@ public function testDeprecatedMethodsFromInterface(): void
141144
$this->assertTrue($class->getNativeMethod('superDeprecated')->isDeprecated()->yes());
142145
}
143146

147+
public function testNotDeprecatedChildMethods(): void
148+
{
149+
$reflectionProvider = $this->createReflectionProvider();
150+
151+
$this->assertTrue($reflectionProvider->getClass(BazInterface::class)->getNativeMethod('superDeprecated')->isDeprecated()->yes());
152+
$this->assertTrue($reflectionProvider->getClass(SubBazInterface::class)->getNativeMethod('superDeprecated')->isDeprecated()->no());
153+
$this->assertTrue($reflectionProvider->getClass(Baz::class)->getNativeMethod('superDeprecated')->isDeprecated()->no());
154+
}
155+
144156
}

Diff for: tests/PHPStan/Reflection/Annotations/data/annotations-deprecated.php

+23
Original file line numberDiff line numberDiff line change
@@ -215,3 +215,26 @@ public function superDeprecated()
215215
}
216216

217217
}
218+
219+
interface BazInterface
220+
{
221+
/**
222+
* @deprecated Use the SubBazInterface instead.
223+
*/
224+
public function superDeprecated();
225+
}
226+
227+
interface SubBazInterface extends BazInterface
228+
{
229+
/**
230+
* @not-deprecated
231+
*/
232+
public function superDeprecated();
233+
}
234+
235+
class Baz implements SubBazInterface
236+
{
237+
public function superDeprecated()
238+
{
239+
}
240+
}

0 commit comments

Comments
 (0)