Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/DependencyInjection/AutowiredParameter.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,14 @@
*
* Works thanks to https://github.com/ondrejmirtes/composer-attribute-collector
* and AutowiredAttributeServicesExtension.
*
* @api
*/
#[Attribute(flags: Attribute::TARGET_PARAMETER)]
final class AutowiredParameter
{

/** @api */
public function __construct(public ?string $ref = null)
{
}
Expand Down
3 changes: 3 additions & 0 deletions src/DependencyInjection/AutowiredService.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,15 @@
*
* Works thanks to https://github.com/ondrejmirtes/composer-attribute-collector
* and AutowiredAttributeServicesExtension.
*
* @api
*/
#[Attribute(flags: Attribute::TARGET_CLASS)]
final class AutowiredService
{

/**
* @api
* @param true|list<class-string>|class-string $as
*/
public function __construct(
Expand Down
3 changes: 3 additions & 0 deletions src/DependencyInjection/Conditional.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
*
* Works thanks to https://github.com/ondrejmirtes/composer-attribute-collector
* and AutowiredAttributeServicesExtension.
*
* @api
*/
#[Attribute(flags: Attribute::TARGET_CLASS)]
final class Conditional
Expand All @@ -29,6 +31,7 @@ final class Conditional
* On PHP < 8.1, simple-downgrader writes `new` in attribute arguments as the array,
* and composer-attribute-collector turns it back into the object in vendor/attributes.php.
*
* @api
* @param AutowiredService|RegisteredCollector|RegisteredRule|array{
* 'olvlvl\ComposerAttributeCollector\NewExpression': array{
* class-string<AutowiredService>,
Expand Down
3 changes: 3 additions & 0 deletions src/DependencyInjection/GenerateFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,15 @@
*
* Works thanks to https://github.com/ondrejmirtes/composer-attribute-collector
* and AutowiredAttributeServicesExtension.
*
* @api
*/
#[Attribute(flags: Attribute::TARGET_CLASS)]
final class GenerateFactory
{

/**
* @api
* @param class-string $interface
* @param class-string $resultType
*/
Expand Down
5 changes: 4 additions & 1 deletion src/DependencyInjection/NonAutowiredService.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,17 @@

/**
* Registers a non-autowired named service in the DI container.

*
* Works thanks to https://github.com/ondrejmirtes/composer-attribute-collector
* and AutowiredAttributeServicesExtension.
*
* @api
*/
#[Attribute(flags: Attribute::TARGET_CLASS)]
final class NonAutowiredService
{

/** @api */
public function __construct(
public string $name,
public ?string $factory = null,
Expand Down
3 changes: 3 additions & 0 deletions src/DependencyInjection/RegisteredCollector.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,14 @@
*
* Works thanks to https://github.com/ondrejmirtes/composer-attribute-collector
* and AutowiredAttributeServicesExtension.
*
* @api
*/
#[Attribute(flags: Attribute::TARGET_CLASS)]
final class RegisteredCollector
{

/** @api */
public function __construct(public int $level)
{
}
Expand Down
3 changes: 3 additions & 0 deletions src/DependencyInjection/RegisteredRule.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,14 @@
*
* Works thanks to https://github.com/ondrejmirtes/composer-attribute-collector
* and AutowiredAttributeServicesExtension.
*
* @api
*/
#[Attribute(flags: Attribute::TARGET_CLASS)]
final class RegisteredRule
{

/** @api */
public function __construct(public int $level)
{
}
Expand Down
32 changes: 32 additions & 0 deletions src/Rules/AttributesCheck.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@
use PHPStan\DependencyInjection\AutowiredParameter;
use PHPStan\DependencyInjection\AutowiredService;
use PHPStan\Internal\SprintfHelper;
use PHPStan\Reflection\ClassReflection;
use PHPStan\Reflection\ParametersAcceptorSelector;
use PHPStan\Reflection\ReflectionProvider;
use PHPStan\Rules\Api\ApiRuleHelper;
use function array_key_exists;
use function count;
use function sprintf;
Expand All @@ -26,6 +28,7 @@ public function __construct(
private ReflectionProvider $reflectionProvider,
private FunctionCallParametersCheck $functionCallParametersCheck,
private ClassNameCheck $classCheck,
private ApiRuleHelper $apiRuleHelper,
#[AutowiredParameter]
private bool $deprecationRulesInstalled,
)
Expand Down Expand Up @@ -66,6 +69,19 @@ public function check(
continue;
}

if (
$this->apiRuleHelper->isPhpStanCode($scope, $attributeClass->getName(), $attributeClass->getFileName())
&& !$this->isApi($attributeClass)
) {
$errors[] = RuleErrorBuilder::message(sprintf(
'Using attribute %s is not covered by backward compatibility promise. The attribute might change in a minor PHPStan version.',
$attributeClass->getDisplayName(),
))->identifier('phpstanApi.attribute')->tip(sprintf(
"If you think it should be covered by backward compatibility promise, open a discussion:\n %s\n\n See also:\n https://phpstan.org/developing-extensions/backward-compatibility-promise",
'https://github.com/phpstan/phpstan/discussions',
))->line($attribute->getStartLine())->build();
}

if ($attributeClass->isAbstract()) {
$errors[] = RuleErrorBuilder::message(sprintf('Attribute class %s is abstract.', $name))
->identifier('attribute.abstract')
Expand Down Expand Up @@ -175,4 +191,20 @@ public function check(
return $errors;
}

private function isApi(ClassReflection $attributeClass): bool
{
$phpDoc = $attributeClass->getResolvedPhpDoc();
if ($phpDoc === null) {
return false;
}

foreach ($phpDoc->getPhpDocNodes() as $phpDocNode) {
if (count($phpDocNode->getTagsByName('@api')) > 0) {
return true;
}
}

return false;
}

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

use PHPStan\Classes\ForbiddenClassNameExtension;
use PHPStan\Rules\Api\ApiRuleHelper;
use PHPStan\Rules\AttributesCheck;
use PHPStan\Rules\ClassCaseSensitivityCheck;
use PHPStan\Rules\ClassForbiddenNameCheck;
Expand Down Expand Up @@ -62,6 +63,7 @@ protected function getRule(): Rule
$reflectionProvider,
$container->getExtensionsCollection(RestrictedClassNameUsageExtension::class),
),
new ApiRuleHelper(),
deprecationRulesInstalled: true,
),
);
Expand Down Expand Up @@ -219,4 +221,17 @@ public function testDeprecatedAttribute(): void
]);
}

#[RequiresPhp('>= 8.1.0')]
public function testPhpStanApiAttributes(): void
{
$tip = "If you think it should be covered by backward compatibility promise, open a discussion:\n https://github.com/phpstan/phpstan/discussions\n\n See also:\n https://phpstan.org/developing-extensions/backward-compatibility-promise";
$this->analyse([__DIR__ . '/data/api-attributes.php'], [
[
'Using attribute PHPStan\\DependencyInjection\\ContainerExtension is not covered by backward compatibility promise. The attribute might change in a minor PHPStan version.',
64,
$tip,
],
]);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace PHPStan\Rules\Classes;

use PHPStan\Classes\ForbiddenClassNameExtension;
use PHPStan\Rules\Api\ApiRuleHelper;
use PHPStan\Rules\AttributesCheck;
use PHPStan\Rules\ClassCaseSensitivityCheck;
use PHPStan\Rules\ClassForbiddenNameCheck;
Expand Down Expand Up @@ -57,6 +58,7 @@ protected function getRule(): Rule
$reflectionProvider,
$container->getExtensionsCollection(RestrictedClassNameUsageExtension::class),
),
new ApiRuleHelper(),
deprecationRulesInstalled: true,
),
);
Expand Down
68 changes: 68 additions & 0 deletions tests/PHPStan/Rules/Classes/data/api-attributes.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
<?php // lint >= 8.1

namespace ApiAttributes;

use PHPStan\DependencyInjection\AutowiredParameter;
use PHPStan\DependencyInjection\AutowiredService;
use PHPStan\DependencyInjection\Conditional;
use PHPStan\DependencyInjection\ContainerExtension;
use PHPStan\DependencyInjection\GenerateFactory;
use PHPStan\DependencyInjection\NonAutowiredService;
use PHPStan\DependencyInjection\RegisteredCollector;
use PHPStan\DependencyInjection\RegisteredRule;

#[AutowiredService]
class Service
{

public function __construct(
#[AutowiredParameter(ref: '%tmpDir%')]
private string $tmpDir,
)
{
}

}

#[RegisteredRule(level: 0)]
class Rule
{

}

#[RegisteredCollector(level: 0)]
class Collector
{

}

#[GenerateFactory(interface: Factory::class)]
class Product
{

}

interface Factory
{

public function create(): Product;

}

#[Conditional(attribute: new RegisteredRule(level: 4), if: '%featureToggles.foo%')]
class ConditionalRule
{

}

#[NonAutowiredService(name: 'foo')]
class NamedService
{

}

#[ContainerExtension(name: 'foo')]
class Extension
{

}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace PHPStan\Rules\Constants;

use PHPStan\Classes\ForbiddenClassNameExtension;
use PHPStan\Rules\Api\ApiRuleHelper;
use PHPStan\Rules\AttributesCheck;
use PHPStan\Rules\ClassCaseSensitivityCheck;
use PHPStan\Rules\ClassForbiddenNameCheck;
Expand Down Expand Up @@ -62,6 +63,7 @@ protected function getRule(): Rule
$reflectionProvider,
$container->getExtensionsCollection(RestrictedClassNameUsageExtension::class),
),
new ApiRuleHelper(),
deprecationRulesInstalled: true,
),
);
Expand Down
2 changes: 2 additions & 0 deletions tests/PHPStan/Rules/Constants/ConstantAttributesRuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace PHPStan\Rules\Constants;

use PHPStan\Classes\ForbiddenClassNameExtension;
use PHPStan\Rules\Api\ApiRuleHelper;
use PHPStan\Rules\AttributesCheck;
use PHPStan\Rules\ClassCaseSensitivityCheck;
use PHPStan\Rules\ClassForbiddenNameCheck;
Expand Down Expand Up @@ -62,6 +63,7 @@ protected function getRule(): Rule
$reflectionProvider,
$container->getExtensionsCollection(RestrictedClassNameUsageExtension::class),
),
new ApiRuleHelper(),
deprecationRulesInstalled: true,
),
);
Expand Down
2 changes: 2 additions & 0 deletions tests/PHPStan/Rules/EnumCases/EnumCaseAttributesRuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace PHPStan\Rules\EnumCases;

use PHPStan\Classes\ForbiddenClassNameExtension;
use PHPStan\Rules\Api\ApiRuleHelper;
use PHPStan\Rules\AttributesCheck;
use PHPStan\Rules\ClassCaseSensitivityCheck;
use PHPStan\Rules\ClassForbiddenNameCheck;
Expand Down Expand Up @@ -58,6 +59,7 @@ protected function getRule(): Rule
$reflectionProvider,
$container->getExtensionsCollection(RestrictedClassNameUsageExtension::class),
),
new ApiRuleHelper(),
deprecationRulesInstalled: true,
),
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace PHPStan\Rules\Functions;

use PHPStan\Classes\ForbiddenClassNameExtension;
use PHPStan\Rules\Api\ApiRuleHelper;
use PHPStan\Rules\AttributesCheck;
use PHPStan\Rules\ClassCaseSensitivityCheck;
use PHPStan\Rules\ClassForbiddenNameCheck;
Expand Down Expand Up @@ -57,6 +58,7 @@ protected function getRule(): Rule
$reflectionProvider,
$container->getExtensionsCollection(RestrictedClassNameUsageExtension::class),
),
new ApiRuleHelper(),
deprecationRulesInstalled: true,
),
);
Expand Down
2 changes: 2 additions & 0 deletions tests/PHPStan/Rules/Functions/ClosureAttributesRuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace PHPStan\Rules\Functions;

use PHPStan\Classes\ForbiddenClassNameExtension;
use PHPStan\Rules\Api\ApiRuleHelper;
use PHPStan\Rules\AttributesCheck;
use PHPStan\Rules\ClassCaseSensitivityCheck;
use PHPStan\Rules\ClassForbiddenNameCheck;
Expand Down Expand Up @@ -57,6 +58,7 @@ protected function getRule(): Rule
$reflectionProvider,
$container->getExtensionsCollection(RestrictedClassNameUsageExtension::class),
),
new ApiRuleHelper(),
deprecationRulesInstalled: true,
),
);
Expand Down
2 changes: 2 additions & 0 deletions tests/PHPStan/Rules/Functions/FunctionAttributesRuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace PHPStan\Rules\Functions;

use PHPStan\Classes\ForbiddenClassNameExtension;
use PHPStan\Rules\Api\ApiRuleHelper;
use PHPStan\Rules\AttributesCheck;
use PHPStan\Rules\ClassCaseSensitivityCheck;
use PHPStan\Rules\ClassForbiddenNameCheck;
Expand Down Expand Up @@ -57,6 +58,7 @@ protected function getRule(): Rule
$reflectionProvider,
$container->getExtensionsCollection(RestrictedClassNameUsageExtension::class),
),
new ApiRuleHelper(),
deprecationRulesInstalled: true,
),
);
Expand Down
Loading
Loading