This repository has been archived by the owner on Oct 8, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 169
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'ntr/custom-phpstan-rules' into '6.1'
NTR - Add custom phpstan rules for Decoratables See merge request shopware/6/product/development!115
- Loading branch information
Showing
9 changed files
with
288 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
17 changes: 17 additions & 0 deletions
17
dev-ops/analyze/src/PHPStan/Rules/AnnotationBasedRuleHelper.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
<?php declare(strict_types=1); | ||
|
||
namespace Shopware\Development\Analyze\PHPStan\Rules; | ||
|
||
use PHPStan\Reflection\ClassReflection; | ||
|
||
class AnnotationBasedRuleHelper | ||
{ | ||
public const DECORATABLE_ANNOTATION = 'Decoratable'; | ||
|
||
public static function isClassTaggedWithAnnotation(ClassReflection $class, string $annotation): bool | ||
{ | ||
$reflection = $class->getNativeReflection(); | ||
|
||
return $reflection->getDocComment() && strpos($reflection->getDocComment(), '@' . $annotation) !== false; | ||
} | ||
} |
50 changes: 50 additions & 0 deletions
50
dev-ops/analyze/src/PHPStan/Rules/Decoratable/DecoratableDoesNotAddPublicMethodRule.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
<?php declare(strict_types=1); | ||
|
||
namespace Shopware\Development\Analyze\PHPStan\Rules\Decoratable; | ||
|
||
use PhpParser\Node; | ||
use PhpParser\Node\Stmt\ClassMethod; | ||
use PHPStan\Analyser\Scope; | ||
use PHPStan\Rules\Rule; | ||
use Shopware\Development\Analyze\PHPStan\Rules\AnnotationBasedRuleHelper; | ||
|
||
class DecoratableDoesNotAddPublicMethodRule implements Rule | ||
{ | ||
public function getNodeType(): string | ||
{ | ||
return ClassMethod::class; | ||
} | ||
|
||
/** | ||
* @param ClassMethod $node | ||
*/ | ||
public function processNode(Node $node, Scope $scope): array | ||
{ | ||
if (!$scope->isInClass()) { | ||
// skip | ||
return []; | ||
} | ||
|
||
$class = $scope->getClassReflection(); | ||
if (!AnnotationBasedRuleHelper::isClassTaggedWithAnnotation($class, AnnotationBasedRuleHelper::DECORATABLE_ANNOTATION)) { | ||
return []; | ||
} | ||
|
||
if (!$node->isPublic() || $node->isMagic()) { | ||
return []; | ||
} | ||
|
||
$method = $class->getMethod($node->name->name, $scope); | ||
|
||
if ($method->getPrototype()->getDeclaringClass()->isInterface()) { | ||
return []; | ||
} | ||
return [ | ||
sprintf( | ||
'The service "%s" is marked as "@Decoratable", but adds public method "%s", that is not defined by any Interface.', | ||
$class->getName(), | ||
$method->getName() | ||
) | ||
]; | ||
} | ||
} |
46 changes: 46 additions & 0 deletions
46
dev-ops/analyze/src/PHPStan/Rules/Decoratable/DecoratableDoesNotCallOwnPublicMethodRule.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
<?php declare(strict_types=1); | ||
|
||
namespace Shopware\Development\Analyze\PHPStan\Rules\Decoratable; | ||
|
||
use PhpParser\Node; | ||
use PhpParser\Node\Expr\MethodCall; | ||
use PHPStan\Analyser\Scope; | ||
use PHPStan\Rules\Rule; | ||
use Shopware\Development\Analyze\PHPStan\Rules\AnnotationBasedRuleHelper; | ||
|
||
class DecoratableDoesNotCallOwnPublicMethodRule implements Rule | ||
{ | ||
public function getNodeType(): string | ||
{ | ||
return MethodCall::class; | ||
} | ||
|
||
/** | ||
* @param MethodCall $node | ||
*/ | ||
public function processNode(Node $node, Scope $scope): array | ||
{ | ||
if (!$scope->isInClass()) { | ||
// skip | ||
return []; | ||
} | ||
|
||
$class = $scope->getClassReflection(); | ||
if (!AnnotationBasedRuleHelper::isClassTaggedWithAnnotation($class, AnnotationBasedRuleHelper::DECORATABLE_ANNOTATION)) { | ||
return []; | ||
} | ||
|
||
$method = $scope->getType($node->var)->getMethod($node->name->name, $scope); | ||
if (!$method->isPublic() || $method->getDeclaringClass()->getName() !== $class->getName()) { | ||
return []; | ||
} | ||
|
||
return [ | ||
sprintf( | ||
'The service "%s" is marked as "@Decoratable", but calls it\'s own public method "%s", which breaks decoration.', | ||
$class->getName(), | ||
$method->getName() | ||
) | ||
]; | ||
} | ||
} |
70 changes: 70 additions & 0 deletions
70
dev-ops/analyze/src/PHPStan/Rules/Decoratable/DecoratableImplementsInterfaceRule.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
<?php declare(strict_types=1); | ||
|
||
namespace Shopware\Development\Analyze\PHPStan\Rules\Decoratable; | ||
|
||
use PhpParser\Node; | ||
use PhpParser\Node\Stmt\Class_; | ||
use PHPStan\Analyser\Scope; | ||
use PHPStan\Broker\Broker; | ||
use PHPStan\Reflection\ClassReflection; | ||
use PHPStan\Rules\Rule; | ||
use Shopware\Development\Analyze\PHPStan\Rules\AnnotationBasedRuleHelper; | ||
|
||
class DecoratableImplementsInterfaceRule implements Rule | ||
{ | ||
/** | ||
* @var Broker | ||
*/ | ||
private $broker; | ||
|
||
public function __construct(Broker $broker) | ||
{ | ||
$this->broker = $broker; | ||
} | ||
|
||
public function getNodeType(): string | ||
{ | ||
return Class_::class; | ||
} | ||
|
||
/** | ||
* @param Class_ $node | ||
*/ | ||
public function processNode(Node $node, Scope $scope): array | ||
{ | ||
if (!$node->namespacedName) { | ||
// skip anonymous classes | ||
return []; | ||
} | ||
|
||
$class = $this->broker->getClass($scope->resolveName($node->namespacedName)); | ||
|
||
if (!AnnotationBasedRuleHelper::isClassTaggedWithAnnotation($class, AnnotationBasedRuleHelper::DECORATABLE_ANNOTATION)) { | ||
return []; | ||
} | ||
|
||
if ($this->implementsInterface($class)) { | ||
return []; | ||
} | ||
|
||
return [ | ||
sprintf( | ||
'The service "%s" is marked as "@Decoratable", but does not implement an interface.', | ||
$class->getName() | ||
) | ||
]; | ||
} | ||
|
||
private function implementsInterface(ClassReflection $class): bool | ||
{ | ||
if (!empty($class->getInterfaces())) { | ||
return true; | ||
} | ||
|
||
if ($class->getParentClass()) { | ||
return $this->implementsInterface($class->getParentClass()); | ||
} | ||
|
||
return false; | ||
} | ||
} |
90 changes: 90 additions & 0 deletions
90
dev-ops/analyze/src/PHPStan/Rules/Decoratable/DecoratableNotDirectlyDependetRule.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
<?php declare(strict_types=1); | ||
|
||
namespace Shopware\Development\Analyze\PHPStan\Rules\Decoratable; | ||
|
||
use PhpParser\Node; | ||
use PhpParser\Node\Expr; | ||
use PhpParser\Node\Stmt\Class_; | ||
use PHPStan\Analyser\Scope; | ||
use PHPStan\Broker\Broker; | ||
use PHPStan\Reflection\ClassReflection; | ||
use PHPStan\Reflection\ParameterReflection; | ||
use PHPStan\Rules\Rule; | ||
use PHPStan\Rules\RuleError; | ||
use PHPStan\Rules\RuleErrorBuilder; | ||
use PHPStan\Type\Type; | ||
use Shopware\Development\Analyze\PHPStan\Rules\AnnotationBasedRuleHelper; | ||
|
||
class DecoratableNotDirectlyDependetRule implements Rule | ||
{ | ||
/** | ||
* @var Broker | ||
*/ | ||
private $broker; | ||
|
||
public function __construct(Broker $broker) | ||
{ | ||
$this->broker = $broker; | ||
} | ||
|
||
public function getNodeType(): string | ||
{ | ||
return Class_::class; | ||
} | ||
|
||
/** | ||
* @param Class_ $node | ||
*/ | ||
public function processNode(Node $node, Scope $scope): array | ||
{ | ||
if (!$node->namespacedName) { | ||
// skip anonymous classes | ||
return []; | ||
} | ||
|
||
$class = $this->broker->getClass($scope->resolveName($node->namespacedName)); | ||
$errors = []; | ||
|
||
foreach ($node->getProperties() as $property) { | ||
foreach ($property->props as $prop) { | ||
$propReflections = $class->getProperty($prop->name->name, $scope); | ||
$this->containsDecoratableTypeDependence($propReflections->getReadableType(), $errors, $class->getName(), $property->getStartLine()); | ||
$this->containsDecoratableTypeDependence($propReflections->getWritableType(), $errors, $class->getName(), $property->getStartLine()); | ||
} | ||
} | ||
|
||
foreach ($node->getMethods() as $method) { | ||
$methodReflection = $class->getMethod($method->name->name, $scope); | ||
foreach ($methodReflection->getVariants() as $variant) { | ||
$this->containsDecoratableTypeDependence($variant->getReturnType(), $errors, $class->getName(), $method->getStartLine()); | ||
|
||
/** @var ParameterReflection $param */ | ||
foreach ($variant->getParameters() as $param) { | ||
$this->containsDecoratableTypeDependence($param->getType(), $errors, $class->getName(), $method->getStartLine()); | ||
} | ||
} | ||
} | ||
|
||
return $errors; | ||
} | ||
|
||
/** | ||
* @param string[]|RuleError[] $errors | ||
*/ | ||
private function containsDecoratableTypeDependence(Type $type, array &$errors, string $originalClassname, int $startLine): void | ||
{ | ||
foreach ($type->getReferencedClasses() as $className) { | ||
$class = $this->broker->getClass($className); | ||
if (!$class->isInterface() && AnnotationBasedRuleHelper::isClassTaggedWithAnnotation($class, AnnotationBasedRuleHelper::DECORATABLE_ANNOTATION)) { | ||
$errors[] = RuleErrorBuilder::message( | ||
sprintf( | ||
'The service "%s" has a direct dependency on decoratable service "%s", but must only depend on it\'s interface.', | ||
$originalClassname, | ||
$class->getName() | ||
) | ||
)->line($startLine) | ||
->build(); | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters