Skip to content

Commit

Permalink
TE-10831 Added rule: any extension of non spryker code must have retu…
Browse files Browse the repository at this point in the history
…rrn types (#112)

* TE-1083 Added rule: any extension of non spryker code must have return types
  • Loading branch information
Dmytro Dymarchuk authored Mar 10, 2022
1 parent b4bbef1 commit 6edac6a
Show file tree
Hide file tree
Showing 12 changed files with 438 additions and 1 deletion.
111 changes: 111 additions & 0 deletions src/Common/Method/ExternalMethodExtensionReturnTypeRule.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
<?php

/**
* Copyright © 2016-present Spryker Systems GmbH. All rights reserved.
* Use of this software requires acceptance of the Evaluation License Agreement. See LICENSE file.
*/

namespace ArchitectureSniffer\Common\Method;

use ArchitectureSniffer\Common\DeprecationTrait;
use ArchitectureSniffer\Common\PhpDocTrait;
use ArchitectureSniffer\Common\PhpTypesTrait;
use PHPMD\AbstractNode;
use PHPMD\AbstractRule;
use PHPMD\Node\ClassNode;
use PHPMD\Rule\ClassAware;
use ReflectionException;
use ReflectionMethod;

class ExternalMethodExtensionReturnTypeRule extends AbstractRule implements ClassAware
{
use DeprecationTrait;
use PhpDocTrait;
use PhpTypesTrait;

/**
* @param \PHPMD\AbstractNode $node
*
* @return void
*/
public function apply(AbstractNode $node): void
{
if (
!$node instanceof ClassNode
|| $this->isClassDeprecated($node)
|| !class_exists($node->getFullQualifiedName())
) {
return;
}

foreach ($node->getMethods() as $methodNode) {
if ($this->isMethodDeprecated($methodNode)) {
continue;
}

$className = $node->getFullQualifiedName();

$reflectionMethod = new ReflectionMethod(
$className,
$methodNode->getName(),
);

if ($reflectionMethod->hasReturnType()) {
continue;
}

$initialMethod = $this->getInitialMethod($reflectionMethod);

if (
$initialMethod === null
|| $initialMethod->getDeclaringClass() === $className
|| $this->isInternalClass($initialMethod->getDeclaringClass()->getName())
) {
continue;
}

$returnType = $this->getReturnTypeByPhpDoc($initialMethod->getDocComment());

if (
$returnType == null
|| $this->isTypeInPhp7NotAllowed($returnType)
) {
continue;
}

$message = sprintf(
'Method `%s` must have return type.',
$methodNode->getFullQualifiedName(),
);

$this->addViolation($methodNode, [$message]);
}
}

/**
* @param string $className
*
* @return bool
*/
protected function isInternalClass(string $className): bool
{
return strpos($className, 'Spryker') === 0;
}

/**
* @param \ReflectionMethod $reflectionMethod
* @param \ReflectionMethod|null $initialReflectionMethod
*
* @return \ReflectionMethod|null
*/
protected function getInitialMethod(ReflectionMethod $reflectionMethod, ?ReflectionMethod $initialReflectionMethod = null): ?ReflectionMethod
{
try {
$prototypeMethod = $reflectionMethod->getPrototype();
} catch (ReflectionException $exception) {
return $initialReflectionMethod ?? null;
}

return $this->getInitialMethod($prototypeMethod, $prototypeMethod);
}
}
3 changes: 2 additions & 1 deletion src/Common/PhpTypesTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ protected function isTypeInPhp7NotAllowed(string $type): bool

return strpos($type, '|') !== false
|| $type === 'mixed'
|| $type === 'false';
|| $type === 'false'
|| $type === 'static';
}
}
8 changes: 8 additions & 0 deletions src/Common/ruleset.xml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,14 @@
<priority>1</priority>
</rule>

<rule
name="ExternalMethodExtensionReturnTypeRule"
message="{0}"
class="ArchitectureSniffer\Common\Method\ExternalMethodExtensionReturnTypeRule">

<priority>1</priority>
</rule>

<!-- COMMON FACTORY RULES -->
<rule
name="FactoryGetContainNoNewRule"
Expand Down
84 changes: 84 additions & 0 deletions tests/Common/Method/ExternalMethodExtensionReturnTypeRuleTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
<?php

/**
* Copyright © 2016-present Spryker Systems GmbH. All rights reserved.
* Use of this software requires acceptance of the Evaluation License Agreement. See LICENSE file.
*/

namespace ArchitectureSnifferTest\Common\Method;

use ArchitectureSniffer\Common\Method\ExternalMethodExtensionReturnTypeRule;
use ArchitectureSnifferTest\AbstractArchitectureSnifferRuleTest;

class ExternalMethodExtensionReturnTypeRuleTest extends AbstractArchitectureSnifferRuleTest
{
/**
* @return void
*/
public function testRuleDoesNotApplyWhenClassExtendInternalAndDoesNotHaveReturnType(): void
{
$bridgePathRule = new ExternalMethodExtensionReturnTypeRule();
$bridgePathRule->setReport($this->getReportMock(0));
$bridgePathRule->apply($this->getClassNode());
}

/**
* @return void
*/
public function testRuleDoesNotApplyWhenClassDoesNotExtendOrImplementExternal(): void
{
$bridgePathRule = new ExternalMethodExtensionReturnTypeRule();
$bridgePathRule->setReport($this->getReportMock(0));
$bridgePathRule->apply($this->getClassNode());
}

/**
* @return void
*/
public function testRuleDoesNotApplyWhenClassExtendsExternalAndHasReturnType(): void
{
$bridgePathRule = new ExternalMethodExtensionReturnTypeRule();
$bridgePathRule->setReport($this->getReportMock(0));
$bridgePathRule->apply($this->getClassNode());
}

/**
* @return void
*/
public function testRuleDoesNotApplyWhenClassImplementsExternalAndHasReturnType(): void
{
$bridgePathRule = new ExternalMethodExtensionReturnTypeRule();
$bridgePathRule->setReport($this->getReportMock(0));
$bridgePathRule->apply($this->getClassNode());
}

/**
* @return void
*/
public function testRuleDoesNotApplyWhenClassExtendsExternalAndDoesNotHaveUnsupportedReturnType(): void
{
$bridgePathRule = new ExternalMethodExtensionReturnTypeRule();
$bridgePathRule->setReport($this->getReportMock(0));
$bridgePathRule->apply($this->getClassNode());
}

/**
* @return void
*/
public function testRuleAppliesWhenClassExtendsExternalAndDoesNotHaveReturnType(): void
{
$bridgePathRule = new ExternalMethodExtensionReturnTypeRule();
$bridgePathRule->setReport($this->getReportMock(1));
$bridgePathRule->apply($this->getClassNode());
}

/**
* @return void
*/
public function testRuleAppliesWhenClassImplementsExternalAndDoesNotHaveReturnType(): void
{
$bridgePathRule = new ExternalMethodExtensionReturnTypeRule();
$bridgePathRule->setReport($this->getReportMock(1));
$bridgePathRule->apply($this->getClassNode());
}
}
7 changes: 7 additions & 0 deletions tests/_bootstrap.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,10 @@
require_once '_data/Common/Bridge/BridgeMethodsInterfaceTest/testRuleAppliesWhenBridgeInterfaceMethodsAreNotCorrect.php';
require_once '_data/Common/Bridge/BridgeMethodsInterfaceTest/testRuleDoesNotApplyWhenBridgeInterfaceMethodsAreCorrect.php';
require_once '_data/Common/Factory/FactoryCreateContainOneNewRuleTest/testRuleAppliesWhenCreateContainsWrongWayOfQueryCreation.php';
require_once '_data/Common/Method/ExternalMethodExtensionReturnTypeRuleTest/testRuleAppliesWhenClassExtendsExternalAndDoesNotHaveReturnType.php';
require_once '_data/Common/Method/ExternalMethodExtensionReturnTypeRuleTest/testRuleAppliesWhenClassImplementsExternalAndDoesNotHaveReturnType.php';
require_once '_data/Common/Method/ExternalMethodExtensionReturnTypeRuleTest/testRuleDoesNotApplyWhenClassExtendInternalAndDoesNotHaveReturnType.php';
require_once '_data/Common/Method/ExternalMethodExtensionReturnTypeRuleTest/testRuleDoesNotApplyWhenClassExtendsExternalAndDoesNotHaveUnsupportedReturnType.php';
require_once '_data/Common/Method/ExternalMethodExtensionReturnTypeRuleTest/testRuleDoesNotApplyWhenClassDoesNotExtendOrImplementExternal.php';
require_once '_data/Common/Method/ExternalMethodExtensionReturnTypeRuleTest/testRuleDoesNotApplyWhenClassImplementsExternalAndHasReturnType.php';
require_once '_data/Common/Method/ExternalMethodExtensionReturnTypeRuleTest/testRuleDoesNotApplyWhenClassExtendsExternalAndHasReturnType.php';
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php

/**
* MIT License
* For full license information, please view the LICENSE file that was distributed with this source code.
*/

namespace ArchitectureSnifferTest\Common\Method\ExternalMethodExtensionReturnTypeRuleTest\TestRuleAppliesWhenClassExtendsExternalAndDoesNotHaveReturnType;

use Spryker\Zed\Module\Business\ModuleFacadeInterface;
use Spryker\Zed\Module\Business;
use TestRuleAppliesWhenClassExtendsExternalAndDoesNotHaveReturnType\Bar;

class Foo extends Bar
{
/**
* @return \TestRuleAppliesWhenClassExtendsExternalAndDoesNotHaveReturnType\Bar
*/
public function foo()
{
return new Bar();
}
}

namespace TestRuleAppliesWhenClassExtendsExternalAndDoesNotHaveReturnType;

class Bar
{
/**
* @return \TestRuleAppliesWhenClassExtendsExternalAndDoesNotHaveReturnType\Bar
*/
public function foo()
{
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

/**
* MIT License
* For full license information, please view the LICENSE file that was distributed with this source code.
*/

namespace ArchitectureSnifferTest\Common\Method\ExternalMethodExtensionReturnTypeRuleTest\TestRuleAppliesWhenClassImplementsExternalAndDoesNotHaveReturnType;

use Spryker\Zed\Module\Business\ModuleFacadeInterface;
use Spryker\Zed\Module\Business;
use TestRuleAppliesWhenClassImplementsExternalAndDoesNotHaveReturnType\Bar;

class Foo implements Bar
{
/**
* @return \TestRuleAppliesWhenClassImplementsExternalAndDoesNotHaveReturnType\Bar
*/
public function foo()
{
}
}

namespace TestRuleAppliesWhenClassImplementsExternalAndDoesNotHaveReturnType;

interface Bar
{
/**
* @return \TestRuleAppliesWhenClassImplementsExternalAndDoesNotHaveReturnType\Bar
*/
public function foo();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php

/**
* MIT License
* For full license information, please view the LICENSE file that was distributed with this source code.
*/

namespace ArchitectureSnifferTest\Common\Method\ExternalMethodExtensionReturnTypeRuleTest\TestRuleDoesNotApplyWhenClassDoesNotExtendOrImplementExternal;

use Spryker\Zed\Module\Business\ModuleFacadeInterface;
use TestRuleDoesNotApplyWhenClassDoesNotExtendOrImplementExternal\Bar;
use TestRuleDoesNotApplyWhenClassDoesNotExtendOrImplementExternal\Baz;

class Foo extends Bar implements Baz
{
/**
* @return \TestRuleDoesNotApplyWhenClassDoesNotExtendOrImplementExternal\Bar
*/
public function foo()
{
return new Bar();
}
}

namespace TestRuleDoesNotApplyWhenClassDoesNotExtendOrImplementExternal;

class Bar
{
}

interface Baz
{
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php

/**
* MIT License
* For full license information, please view the LICENSE file that was distributed with this source code.
*/

namespace ArchitectureSnifferTest\Common\Method\ExternalMethodExtensionReturnTypeRuleTest\TestRuleDoesNotApplyWhenClassExtendInternalAndDoesNotHaveReturnType;

use Spryker\TestRuleDoesNotApplyWhenClassExtendInternalAndDoesNotHaveReturnType\Bar;
use Spryker\Zed\Module\Business\ModuleFacadeInterface;
use Spryker\Zed\Module\Business;

class Foo extends Bar
{
/**
* @return bool
*/
public function foo()
{
}
}

namespace Spryker\TestRuleDoesNotApplyWhenClassExtendInternalAndDoesNotHaveReturnType;

class Bar
{
/**
* @return bool
*/
public function foo()
{
}
}
Loading

0 comments on commit 6edac6a

Please sign in to comment.