diff --git a/src/Rules/Api/ApiClassConstFetchRule.php b/src/Rules/Api/ApiClassConstFetchRule.php index 785c29d64f8..94e3253877c 100644 --- a/src/Rules/Api/ApiClassConstFetchRule.php +++ b/src/Rules/Api/ApiClassConstFetchRule.php @@ -5,6 +5,7 @@ use PhpParser\Node; use PHPStan\Analyser\Scope; use PHPStan\DependencyInjection\RegisteredRule; +use PHPStan\Reflection\ClassConstantReflection; use PHPStan\Reflection\ReflectionProvider; use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; @@ -60,6 +61,12 @@ public function processNode(Node $node, Scope $scope): array 'https://github.com/phpstan/phpstan/discussions', ))->build(); + if ($node->name->toLowerString() !== 'class' && $classReflection->hasConstant($node->name->toString())) { + if ($this->isConstantCovered($classReflection->getConstant($node->name->toString()))) { + return []; + } + } + $docBlock = $classReflection->getResolvedPhpDoc(); if ($docBlock !== null) { foreach ($docBlock->getPhpDocNodes() as $phpDocNode) { @@ -88,4 +95,24 @@ public function processNode(Node $node, Scope $scope): array return [$ruleError]; } + private function isConstantCovered(ClassConstantReflection $constantReflection): bool + { + $classDocBlock = $constantReflection->getDeclaringClass()->getResolvedPhpDoc(); + if ($classDocBlock !== null) { + foreach ($classDocBlock->getPhpDocNodes() as $phpDocNode) { + $apiTags = $phpDocNode->getTagsByName('@api'); + if (count($apiTags) > 0) { + return true; + } + } + } + + $constantDocComment = $constantReflection->getDocComment(); + if ($constantDocComment === null) { + return false; + } + + return str_contains($constantDocComment, '@api'); + } + } diff --git a/src/Rules/Api/ApiTraitUseRule.php b/src/Rules/Api/ApiTraitUseRule.php index 796d745bab4..e688e6eebbd 100644 --- a/src/Rules/Api/ApiTraitUseRule.php +++ b/src/Rules/Api/ApiTraitUseRule.php @@ -8,6 +8,7 @@ use PHPStan\Reflection\ReflectionProvider; use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; +use function count; use function sprintf; /** @@ -47,6 +48,15 @@ public function processNode(Node $node, Scope $scope): array continue; } + $docBlock = $traitReflection->getResolvedPhpDoc(); + if ($docBlock !== null) { + foreach ($docBlock->getPhpDocNodes() as $phpDocNode) { + if (count($phpDocNode->getTagsByName('@api')) > 0) { + continue 2; + } + } + } + $errors[] = RuleErrorBuilder::message(sprintf( 'Using %s is not covered by backward compatibility promise. The trait might change in a minor PHPStan version.', $traitReflection->getDisplayName(), diff --git a/src/Turbo/TurboExtensionEnabler.php b/src/Turbo/TurboExtensionEnabler.php index 47c39e174f3..4272774259d 100644 --- a/src/Turbo/TurboExtensionEnabler.php +++ b/src/Turbo/TurboExtensionEnabler.php @@ -122,6 +122,8 @@ private static function isCompatible(): bool /** * Whether activateIfCompatible() declared the native classes — the * shadowed class names resolve to them only in that case. + * + * @api */ public static function isActive(): bool { diff --git a/src/Turbo/TurboExtensionSelector.php b/src/Turbo/TurboExtensionSelector.php index 321c3f4d9aa..0137256b7ed 100644 --- a/src/Turbo/TurboExtensionSelector.php +++ b/src/Turbo/TurboExtensionSelector.php @@ -58,6 +58,7 @@ final class TurboExtensionSelector */ public const MINIMUM_PHP_VERSION_ID = 80300; + /** @api */ public const EXTENSION_PATH_INI = 'phpstan.turboExtensionPath'; /** @@ -75,6 +76,13 @@ public static function getRestartExtensionPath(): ?string return $path; } + /** + * The extension path a spawned worker needs as `-d extension=`, along with + * `-d` EXTENSION_PATH_INI with the same path. Null when the worker gets the + * extension from the php.ini it inherits, or when there is no extension. + * + * @api + */ public static function findExtensionForWorkers(?string $pharPath = null): ?string { if (TurboExtensionEnabler::isLoaded()) { diff --git a/src/Type/Traits/LateResolvableTypeTrait.php b/src/Type/Traits/LateResolvableTypeTrait.php index f7d79c79427..f3b3abb3ebe 100644 --- a/src/Type/Traits/LateResolvableTypeTrait.php +++ b/src/Type/Traits/LateResolvableTypeTrait.php @@ -22,6 +22,7 @@ use PHPStan\Type\NeverType; use PHPStan\Type\Type; +/** @api */ trait LateResolvableTypeTrait { diff --git a/tests/PHPStan/Rules/Api/ApiClassConstFetchRuleTest.php b/tests/PHPStan/Rules/Api/ApiClassConstFetchRuleTest.php index b139a1ef2a3..82fdc821ccd 100644 --- a/tests/PHPStan/Rules/Api/ApiClassConstFetchRuleTest.php +++ b/tests/PHPStan/Rules/Api/ApiClassConstFetchRuleTest.php @@ -32,12 +32,17 @@ public function testRuleOutOfPhpStan(): void $this->analyse([__DIR__ . '/data/class-const-fetch-out-of-phpstan.php'], [ [ 'Accessing PHPStan\Command\AnalyseCommand::class is not covered by backward compatibility promise. The class might change in a minor PHPStan version.', - 16, + 19, $tip, ], [ 'Accessing PHPStan\Analyser\NodeScopeResolver::FOO is not covered by backward compatibility promise. The class might change in a minor PHPStan version.', - 20, + 23, + $tip, + ], + [ + 'Accessing PHPStan\Turbo\TurboExtensionSelector::MINIMUM_PHP_VERSION_ID is not covered by backward compatibility promise. The class might change in a minor PHPStan version.', + 28, $tip, ], ]); diff --git a/tests/PHPStan/Rules/Api/ApiTraitUseRuleTest.php b/tests/PHPStan/Rules/Api/ApiTraitUseRuleTest.php index d15bbf5aa1d..bebf04977ee 100644 --- a/tests/PHPStan/Rules/Api/ApiTraitUseRuleTest.php +++ b/tests/PHPStan/Rules/Api/ApiTraitUseRuleTest.php @@ -32,7 +32,7 @@ public function testRuleOutOfPhpStan(): void $this->analyse([__DIR__ . '/data/trait-use-out-of-phpstan.php'], [ [ 'Using PHPStan\Type\JustNullableTypeTrait is not covered by backward compatibility promise. The trait might change in a minor PHPStan version.', - 15, + 16, $tip, ], ]); diff --git a/tests/PHPStan/Rules/Api/data/class-const-fetch-out-of-phpstan.php b/tests/PHPStan/Rules/Api/data/class-const-fetch-out-of-phpstan.php index 03cdd9f94a0..12bacacac1b 100644 --- a/tests/PHPStan/Rules/Api/data/class-const-fetch-out-of-phpstan.php +++ b/tests/PHPStan/Rules/Api/data/class-const-fetch-out-of-phpstan.php @@ -2,10 +2,13 @@ namespace App\ClassConstFetch; +use PHPStan\Analyser\MutatingScope; use PHPStan\Analyser\NodeScopeResolver; +use PHPStan\Analyser\Scope; use PHPStan\Analyser\ScopeFactory; use PHPStan\Command\AnalyseCommand; use PHPStan\Reflection\ClassReflection; +use PHPStan\Turbo\TurboExtensionSelector; class Foo { @@ -18,6 +21,11 @@ public function doFoo() echo ScopeFactory::class; echo NodeScopeResolver::FOO; + + echo Scope::SUPERGLOBAL_VARIABLES; + echo MutatingScope::SUPERGLOBAL_VARIABLES; + echo TurboExtensionSelector::EXTENSION_PATH_INI; + echo TurboExtensionSelector::MINIMUM_PHP_VERSION_ID; } } diff --git a/tests/PHPStan/Rules/Api/data/trait-use-out-of-phpstan.php b/tests/PHPStan/Rules/Api/data/trait-use-out-of-phpstan.php index 966fe881a05..a358da2db6a 100644 --- a/tests/PHPStan/Rules/Api/data/trait-use-out-of-phpstan.php +++ b/tests/PHPStan/Rules/Api/data/trait-use-out-of-phpstan.php @@ -3,6 +3,7 @@ namespace App\TraitUse; use PHPStan\Type\JustNullableTypeTrait; +use PHPStan\Type\Traits\LateResolvableTypeTrait; trait FooTrait { @@ -16,3 +17,10 @@ class Foo use FooTrait; } + +class Bar +{ + + use LateResolvableTypeTrait; + +}