Skip to content

Commit 752d229

Browse files
authored
DeprecationExtensions: allow custom deprecation-marking logic
1 parent aa4527b commit 752d229

26 files changed

+1023
-36
lines changed

Diff for: build/enums.neon

+4
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,7 @@ parameters:
1313
paths:
1414
- ../tests/PHPStan/Type/ObjectTypeTest.php
1515
- ../tests/PHPStan/Type/IntersectionTypeTest.php
16+
-
17+
message: '#^Class CustomDeprecations\\MyDeprecatedEnum not found\.$#'
18+
paths:
19+
- ../tests/PHPStan/Reflection/Deprecation/DeprecationProviderTest.php

Diff for: conf/config.neon

+3
Original file line numberDiff line numberDiff line change
@@ -2158,6 +2158,9 @@ services:
21582158
-
21592159
class: PHPStan\Reflection\BetterReflection\SourceStubber\ReflectionSourceStubberFactory
21602160

2161+
-
2162+
class: PHPStan\Reflection\Deprecation\DeprecationProvider
2163+
21612164
php8Lexer:
21622165
class: PhpParser\Lexer\Emulative
21632166
factory: @PHPStan\Parser\LexerFactory::createEmulative()

Diff for: src/Analyser/NodeScopeResolver.php

+3
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@
137137
use PHPStan\Reflection\Callables\SimpleImpurePoint;
138138
use PHPStan\Reflection\Callables\SimpleThrowPoint;
139139
use PHPStan\Reflection\ClassReflection;
140+
use PHPStan\Reflection\Deprecation\DeprecationProvider;
140141
use PHPStan\Reflection\ExtendedMethodReflection;
141142
use PHPStan\Reflection\ExtendedParameterReflection;
142143
use PHPStan\Reflection\ExtendedParametersAcceptor;
@@ -256,6 +257,7 @@ public function __construct(
256257
private readonly StubPhpDocProvider $stubPhpDocProvider,
257258
private readonly PhpVersion $phpVersion,
258259
private readonly SignatureMapProvider $signatureMapProvider,
260+
private readonly DeprecationProvider $deprecationProvider,
259261
private readonly AttributeReflectionFactory $attributeReflectionFactory,
260262
private readonly PhpDocInheritanceResolver $phpDocInheritanceResolver,
261263
private readonly FileHelper $fileHelper,
@@ -2193,6 +2195,7 @@ private function createAstClassReflection(Node\Stmt\ClassLike $stmt, string $cla
21932195
$this->phpDocInheritanceResolver,
21942196
$this->phpVersion,
21952197
$this->signatureMapProvider,
2198+
$this->deprecationProvider,
21962199
$this->attributeReflectionFactory,
21972200
$this->classReflectionExtensionRegistryProvider->getRegistry()->getPropertiesClassReflectionExtensions(),
21982201
$this->classReflectionExtensionRegistryProvider->getRegistry()->getMethodsClassReflectionExtensions(),

Diff for: src/DependencyInjection/ConditionalTagsExtension.php

+12
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,12 @@
1616
use PHPStan\Parser\RichParser;
1717
use PHPStan\PhpDoc\StubFilesExtension;
1818
use PHPStan\PhpDoc\TypeNodeResolverExtension;
19+
use PHPStan\Reflection\Deprecation\ClassConstantDeprecationExtension;
20+
use PHPStan\Reflection\Deprecation\ClassDeprecationExtension;
21+
use PHPStan\Reflection\Deprecation\EnumCaseDeprecationExtension;
22+
use PHPStan\Reflection\Deprecation\FunctionDeprecationExtension;
23+
use PHPStan\Reflection\Deprecation\MethodDeprecationExtension;
24+
use PHPStan\Reflection\Deprecation\PropertyDeprecationExtension;
1925
use PHPStan\Rules\Constants\AlwaysUsedClassConstantsExtensionProvider;
2026
use PHPStan\Rules\LazyRegistry;
2127
use PHPStan\Rules\Properties\ReadWritePropertiesExtensionProvider;
@@ -61,6 +67,12 @@ public function getConfigSchema(): Nette\Schema\Schema
6167
LazyParameterOutTypeExtensionProvider::STATIC_METHOD_TAG => $bool,
6268
DiagnoseExtension::EXTENSION_TAG => $bool,
6369
ResultCacheMetaExtension::EXTENSION_TAG => $bool,
70+
ClassConstantDeprecationExtension::CLASS_CONSTANT_EXTENSION_TAG => $bool,
71+
ClassDeprecationExtension::CLASS_EXTENSION_TAG => $bool,
72+
EnumCaseDeprecationExtension::ENUM_CASE_EXTENSION_TAG => $bool,
73+
FunctionDeprecationExtension::FUNCTION_EXTENSION_TAG => $bool,
74+
MethodDeprecationExtension::METHOD_EXTENSION_TAG => $bool,
75+
PropertyDeprecationExtension::PROPERTY_EXTENSION_TAG => $bool,
6476
])->min(1));
6577
}
6678

Diff for: src/Reflection/BetterReflection/BetterReflectionProvider.php

+23-12
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
use PHPStan\Reflection\ClassReflection;
3737
use PHPStan\Reflection\Constant\RuntimeConstantReflection;
3838
use PHPStan\Reflection\ConstantReflection;
39+
use PHPStan\Reflection\Deprecation\DeprecationProvider;
3940
use PHPStan\Reflection\FunctionReflection;
4041
use PHPStan\Reflection\FunctionReflectionFactory;
4142
use PHPStan\Reflection\InitializerExprContext;
@@ -85,6 +86,7 @@ public function __construct(
8586
private Reflector $reflector,
8687
private FileTypeMapper $fileTypeMapper,
8788
private PhpDocInheritanceResolver $phpDocInheritanceResolver,
89+
private DeprecationProvider $deprecationProvider,
8890
private PhpVersion $phpVersion,
8991
private NativeFunctionReflectionProvider $nativeFunctionReflectionProvider,
9092
private StubPhpDocProvider $stubPhpDocProvider,
@@ -148,6 +150,7 @@ public function getClass(string $className): ClassReflection
148150
$this->phpDocInheritanceResolver,
149151
$this->phpVersion,
150152
$this->signatureMapProvider,
153+
$this->deprecationProvider,
151154
$this->attributeReflectionFactory,
152155
$this->classReflectionExtensionRegistryProvider->getRegistry()->getPropertiesClassReflectionExtensions(),
153156
$this->classReflectionExtensionRegistryProvider->getRegistry()->getMethodsClassReflectionExtensions(),
@@ -243,6 +246,7 @@ public function getAnonymousClassReflection(Node\Stmt\Class_ $classNode, Scope $
243246
$this->phpDocInheritanceResolver,
244247
$this->phpVersion,
245248
$this->signatureMapProvider,
249+
$this->deprecationProvider,
246250
$this->attributeReflectionFactory,
247251
$this->classReflectionExtensionRegistryProvider->getRegistry()->getPropertiesClassReflectionExtensions(),
248252
$this->classReflectionExtensionRegistryProvider->getRegistry()->getMethodsClassReflectionExtensions(),
@@ -305,8 +309,11 @@ private function getCustomFunction(string $functionName): PhpFunctionReflection
305309
$phpDocParameterTypes = [];
306310
$phpDocReturnTag = null;
307311
$phpDocThrowsTag = null;
308-
$deprecatedTag = null;
309-
$isDeprecated = false;
312+
313+
$deprecation = $this->deprecationProvider->getFunctionDeprecation($reflectionFunction);
314+
$deprecationDescription = $deprecation === null ? null : $deprecation->getDescription();
315+
$isDeprecated = $deprecation !== null;
316+
310317
$isInternal = false;
311318
$isPure = null;
312319
$asserts = Assertions::createEmpty();
@@ -327,8 +334,10 @@ private function getCustomFunction(string $functionName): PhpFunctionReflection
327334
$phpDocParameterTypes = array_map(static fn ($tag) => $tag->getType(), $resolvedPhpDoc->getParamTags());
328335
$phpDocReturnTag = $resolvedPhpDoc->getReturnTag();
329336
$phpDocThrowsTag = $resolvedPhpDoc->getThrowsTag();
330-
$deprecatedTag = $resolvedPhpDoc->getDeprecatedTag();
331-
$isDeprecated = $resolvedPhpDoc->isDeprecated();
337+
if (!$isDeprecated) {
338+
$deprecationDescription = $resolvedPhpDoc->getDeprecatedTag() !== null ? $resolvedPhpDoc->getDeprecatedTag()->getMessage() : $deprecationDescription;
339+
$isDeprecated = $resolvedPhpDoc->isDeprecated();
340+
}
332341
$isInternal = $resolvedPhpDoc->isInternal();
333342
$isPure = $resolvedPhpDoc->isPure();
334343
$asserts = Assertions::createFromResolvedPhpDocBlock($resolvedPhpDoc);
@@ -347,7 +356,7 @@ private function getCustomFunction(string $functionName): PhpFunctionReflection
347356
$phpDocParameterTypes,
348357
$phpDocReturnTag !== null ? $phpDocReturnTag->getType() : null,
349358
$phpDocThrowsTag !== null ? $phpDocThrowsTag->getType() : null,
350-
$deprecatedTag !== null ? $deprecatedTag->getMessage() : null,
359+
$deprecationDescription,
351360
$isDeprecated,
352361
$isInternal,
353362
$reflectionFunction->getFileName() !== false ? $reflectionFunction->getFileName() : null,
@@ -407,13 +416,15 @@ public function getConstant(Node\Name $nameNode, ?NamespaceAnswerer $namespaceAn
407416
$constantValueType = $this->initializerExprTypeResolver->getType($constantReflection->getValueExpression(), InitializerExprContext::fromGlobalConstant($constantReflection));
408417
$docComment = $constantReflection->getDocComment();
409418

410-
$isDeprecated = TrinaryLogic::createNo();
411-
$deprecatedDescription = null;
412-
if ($docComment !== null) {
419+
$deprecation = $this->deprecationProvider->getConstantDeprecation($constantReflection);
420+
$isDeprecated = $deprecation !== null;
421+
$deprecatedDescription = $deprecation === null ? null : $deprecation->getDescription();
422+
423+
if ($isDeprecated === false && $docComment !== null) {
413424
$resolvedPhpDoc = $this->fileTypeMapper->getResolvedPhpDoc($fileName, null, null, null, $docComment);
414-
$isDeprecated = TrinaryLogic::createFromBoolean($resolvedPhpDoc->isDeprecated());
425+
$isDeprecated = $resolvedPhpDoc->isDeprecated();
415426

416-
if ($resolvedPhpDoc->isDeprecated() && $resolvedPhpDoc->getDeprecatedTag() !== null) {
427+
if ($isDeprecated && $resolvedPhpDoc->getDeprecatedTag() !== null) {
417428
$deprecatedMessage = $resolvedPhpDoc->getDeprecatedTag()->getMessage();
418429

419430
$matches = Strings::match($deprecatedMessage ?? '', '#^(\d+)\.(\d+)(?:\.(\d+))?$#');
@@ -423,7 +434,7 @@ public function getConstant(Node\Name $nameNode, ?NamespaceAnswerer $namespaceAn
423434
$patch = $matches[3] ?? 0;
424435
$versionId = sprintf('%d%02d%02d', $major, $minor, $patch);
425436

426-
$isDeprecated = TrinaryLogic::createFromBoolean($this->phpVersion->getVersionId() >= $versionId);
437+
$isDeprecated = $this->phpVersion->getVersionId() >= $versionId;
427438
} else {
428439
// filter raw version number messages like in
429440
// https://github.com/JetBrains/phpstorm-stubs/blob/9608c953230b08f07b703ecfe459cc58d5421437/filter/filter.php#L478
@@ -436,7 +447,7 @@ public function getConstant(Node\Name $nameNode, ?NamespaceAnswerer $namespaceAn
436447
$constantName,
437448
$constantValueType,
438449
$fileName,
439-
$isDeprecated,
450+
TrinaryLogic::createFromBoolean($isDeprecated),
440451
$deprecatedDescription,
441452
);
442453
}

Diff for: src/Reflection/ClassReflection.php

+45-11
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
use PHPStan\PhpDoc\Tag\TemplateTag;
2727
use PHPStan\PhpDoc\Tag\TypeAliasImportTag;
2828
use PHPStan\PhpDoc\Tag\TypeAliasTag;
29+
use PHPStan\Reflection\Deprecation\DeprecationProvider;
2930
use PHPStan\Reflection\Php\PhpClassReflectionExtension;
3031
use PHPStan\Reflection\Php\PhpPropertyReflection;
3132
use PHPStan\Reflection\Php\UniversalObjectCratesClassReflectionExtension;
@@ -161,6 +162,7 @@ public function __construct(
161162
private PhpDocInheritanceResolver $phpDocInheritanceResolver,
162163
private PhpVersion $phpVersion,
163164
private SignatureMapProvider $signatureMapProvider,
165+
private DeprecationProvider $deprecationProvider,
164166
private AttributeReflectionFactory $attributeReflectionFactory,
165167
private array $propertiesClassReflectionExtensions,
166168
private array $methodsClassReflectionExtensions,
@@ -793,7 +795,8 @@ public function getEnumCases(): array
793795
$valueType = $this->initializerExprTypeResolver->getType($case->getValueExpression(), $initializerExprContext);
794796
}
795797
$caseName = $case->getName();
796-
$cases[$caseName] = new EnumCaseReflection($this, $case, $valueType, $this->attributeReflectionFactory->fromNativeReflection($case->getAttributes(), InitializerExprContext::fromClass($this->getName(), $this->getFileName())));
798+
$attributes = $this->attributeReflectionFactory->fromNativeReflection($case->getAttributes(), InitializerExprContext::fromClass($this->getName(), $this->getFileName()));
799+
$cases[$caseName] = new EnumCaseReflection($this, $case, $valueType, $attributes, $this->deprecationProvider);
797800
}
798801

799802
return $this->enumCases = $cases;
@@ -819,7 +822,9 @@ public function getEnumCase(string $name): EnumCaseReflection
819822
$valueType = $this->initializerExprTypeResolver->getType($case->getValueExpression(), InitializerExprContext::fromClassReflection($this));
820823
}
821824

822-
return new EnumCaseReflection($this, $case, $valueType, $this->attributeReflectionFactory->fromNativeReflection($case->getAttributes(), InitializerExprContext::fromClass($this->getName(), $this->getFileName())));
825+
$attributes = $this->attributeReflectionFactory->fromNativeReflection($case->getAttributes(), InitializerExprContext::fromClass($this->getName(), $this->getFileName()));
826+
827+
return new EnumCaseReflection($this, $case, $valueType, $attributes, $this->deprecationProvider);
823828
}
824829

825830
public function isClass(): bool
@@ -1079,6 +1084,10 @@ public function getConstant(string $name): ClassConstantReflection
10791084
throw new MissingConstantFromReflectionException($this->getName(), $name);
10801085
}
10811086

1087+
$deprecation = $this->deprecationProvider->getClassConstantDeprecation($reflectionConstant);
1088+
$deprecatedDescription = $deprecation === null ? null : $deprecation->getDescription();
1089+
$isDeprecated = $deprecation !== null;
1090+
10821091
$declaringClass = $this->reflectionProvider->getClass($reflectionConstant->getDeclaringClass()->getName());
10831092
$fileName = $declaringClass->getFileName();
10841093
$phpDocType = null;
@@ -1099,8 +1108,10 @@ public function getConstant(string $name): ClassConstantReflection
10991108
);
11001109
}
11011110

1102-
$deprecatedDescription = $resolvedPhpDoc->getDeprecatedTag() !== null ? $resolvedPhpDoc->getDeprecatedTag()->getMessage() : null;
1103-
$isDeprecated = $resolvedPhpDoc->isDeprecated();
1111+
if (!$isDeprecated) {
1112+
$deprecatedDescription = $resolvedPhpDoc->getDeprecatedTag() !== null ? $resolvedPhpDoc->getDeprecatedTag()->getMessage() : null;
1113+
$isDeprecated = $resolvedPhpDoc->isDeprecated();
1114+
}
11041115
$isInternal = $resolvedPhpDoc->isInternal();
11051116
$isFinal = $resolvedPhpDoc->isFinal();
11061117
$varTags = $resolvedPhpDoc->getVarTags();
@@ -1210,11 +1221,8 @@ public function getTypeAliases(): array
12101221

12111222
public function getDeprecatedDescription(): ?string
12121223
{
1213-
if ($this->deprecatedDescription === null && $this->isDeprecated()) {
1214-
$resolvedPhpDoc = $this->getResolvedPhpDoc();
1215-
if ($resolvedPhpDoc !== null && $resolvedPhpDoc->getDeprecatedTag() !== null) {
1216-
$this->deprecatedDescription = $resolvedPhpDoc->getDeprecatedTag()->getMessage();
1217-
}
1224+
if ($this->isDeprecated === null) {
1225+
$this->resolveDeprecation();
12181226
}
12191227

12201228
return $this->deprecatedDescription;
@@ -1223,13 +1231,36 @@ public function getDeprecatedDescription(): ?string
12231231
public function isDeprecated(): bool
12241232
{
12251233
if ($this->isDeprecated === null) {
1226-
$resolvedPhpDoc = $this->getResolvedPhpDoc();
1227-
$this->isDeprecated = $resolvedPhpDoc !== null && $resolvedPhpDoc->isDeprecated();
1234+
$this->resolveDeprecation();
12281235
}
12291236

12301237
return $this->isDeprecated;
12311238
}
12321239

1240+
/**
1241+
* @phpstan-assert bool $this->isDeprecated
1242+
*/
1243+
private function resolveDeprecation(): void
1244+
{
1245+
$deprecation = $this->deprecationProvider->getClassDeprecation($this->reflection);
1246+
if ($deprecation !== null) {
1247+
$this->isDeprecated = true;
1248+
$this->deprecatedDescription = $deprecation->getDescription();
1249+
return;
1250+
}
1251+
1252+
$resolvedPhpDoc = $this->getResolvedPhpDoc();
1253+
1254+
if ($resolvedPhpDoc !== null && $resolvedPhpDoc->isDeprecated()) {
1255+
$this->isDeprecated = true;
1256+
$this->deprecatedDescription = $resolvedPhpDoc->getDeprecatedTag() !== null ? $resolvedPhpDoc->getDeprecatedTag()->getMessage() : null;
1257+
return;
1258+
}
1259+
1260+
$this->isDeprecated = false;
1261+
$this->deprecatedDescription = null;
1262+
}
1263+
12331264
public function isBuiltin(): bool
12341265
{
12351266
return $this->reflection->isInternal();
@@ -1559,6 +1590,7 @@ public function withTypes(array $types): self
15591590
$this->phpDocInheritanceResolver,
15601591
$this->phpVersion,
15611592
$this->signatureMapProvider,
1593+
$this->deprecationProvider,
15621594
$this->attributeReflectionFactory,
15631595
$this->propertiesClassReflectionExtensions,
15641596
$this->methodsClassReflectionExtensions,
@@ -1590,6 +1622,7 @@ public function withVariances(array $variances): self
15901622
$this->phpDocInheritanceResolver,
15911623
$this->phpVersion,
15921624
$this->signatureMapProvider,
1625+
$this->deprecationProvider,
15931626
$this->attributeReflectionFactory,
15941627
$this->propertiesClassReflectionExtensions,
15951628
$this->methodsClassReflectionExtensions,
@@ -1631,6 +1664,7 @@ public function asFinal(): self
16311664
$this->phpDocInheritanceResolver,
16321665
$this->phpVersion,
16331666
$this->signatureMapProvider,
1667+
$this->deprecationProvider,
16341668
$this->attributeReflectionFactory,
16351669
$this->propertiesClassReflectionExtensions,
16361670
$this->methodsClassReflectionExtensions,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Reflection\Deprecation;
4+
5+
use PHPStan\BetterReflection\Reflection\Adapter\ReflectionClassConstant;
6+
7+
/**
8+
* This interface allows you to provide custom deprecation information
9+
*
10+
* To register it in the configuration file use the following tag:
11+
*
12+
* ```
13+
* services:
14+
* -
15+
* class: App\PHPStan\MyProvider
16+
* tags:
17+
* - phpstan.classConstantDeprecationExtension
18+
* ```
19+
*
20+
* @api
21+
*/
22+
interface ClassConstantDeprecationExtension
23+
{
24+
25+
public const CLASS_CONSTANT_EXTENSION_TAG = 'phpstan.classConstantDeprecationExtension';
26+
27+
public function getClassConstantDeprecation(ReflectionClassConstant $reflection): ?Deprecation;
28+
29+
}
+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Reflection\Deprecation;
4+
5+
use PHPStan\BetterReflection\Reflection\Adapter\ReflectionClass;
6+
use PHPStan\BetterReflection\Reflection\Adapter\ReflectionEnum;
7+
8+
/**
9+
* This interface allows you to provide custom deprecation information
10+
*
11+
* To register it in the configuration file use the following tag:
12+
*
13+
* ```
14+
* services:
15+
* -
16+
* class: App\PHPStan\MyProvider
17+
* tags:
18+
* - phpstan.classDeprecationExtension
19+
* ```
20+
*
21+
* @api
22+
*/
23+
interface ClassDeprecationExtension
24+
{
25+
26+
public const CLASS_EXTENSION_TAG = 'phpstan.classDeprecationExtension';
27+
28+
public function getClassDeprecation(ReflectionClass|ReflectionEnum $reflection): ?Deprecation;
29+
30+
}

0 commit comments

Comments
 (0)