Skip to content

Commit

Permalink
Stop traversal of interfaces and abstract methods (#656)
Browse files Browse the repository at this point in the history
* Stop traversal of interfaces and abstract methods

* Remove interface and abstraction checks in public visitor
  • Loading branch information
BackEndTea authored and maks-rafalko committed Mar 10, 2019
1 parent b89d230 commit 5b02e8e
Show file tree
Hide file tree
Showing 6 changed files with 144 additions and 41 deletions.
2 changes: 2 additions & 0 deletions src/Mutant/Generator/MutationsGenerator.php
Expand Up @@ -47,6 +47,7 @@
use Infection\Traverser\PriorityNodeTraverser;
use Infection\Visitor\FullyQualifiedClassNameVisitor;
use Infection\Visitor\MutationsCollectorVisitor;
use Infection\Visitor\NotMutableIgnoreVisitor;
use Infection\Visitor\ParentConnectorVisitor;
use Infection\Visitor\ReflectionVisitor;
use PhpParser\Node;
Expand Down Expand Up @@ -159,6 +160,7 @@ private function getMutationsFromFile(SplFileInfo $file, bool $onlyCovered, arra
$onlyCovered
);

$traverser->addVisitor(new NotMutableIgnoreVisitor(), 50);
$traverser->addVisitor(new ParentConnectorVisitor(), 40);
$traverser->addVisitor(new FullyQualifiedClassNameVisitor(), 30);
$traverser->addVisitor(new ReflectionVisitor(), 20);
Expand Down
11 changes: 0 additions & 11 deletions src/Mutator/FunctionSignature/PublicVisibility.php
Expand Up @@ -35,7 +35,6 @@

namespace Infection\Mutator\FunctionSignature;

use Infection\Mutator\Util\InterfaceParentTrait;
use Infection\Mutator\Util\Mutator;
use Infection\Visitor\ReflectionVisitor;
use PhpParser\Node;
Expand All @@ -47,8 +46,6 @@
*/
final class PublicVisibility extends Mutator
{
use InterfaceParentTrait;

/**
* Replaces "public function..." with "protected function ..."
*
Expand Down Expand Up @@ -81,18 +78,10 @@ protected function mutatesNode(Node $node): bool
return false;
}

if ($node->isAbstract()) {
return false;
}

if ($node->isMagic()) {
return false;
}

if ($this->isBelongsToInterface($node)) {
return false;
}

return !$this->hasSamePublicParentMethod($node);
}

Expand Down
15 changes: 1 addition & 14 deletions src/Visitor/MutationsCollectorVisitor.php
Expand Up @@ -50,7 +50,7 @@ final class MutationsCollectorVisitor extends NodeVisitorAbstract
/**
* @var Mutator[]
*/
private $mutators = [];
private $mutators;

/**
* @var Mutation[]
Expand Down Expand Up @@ -109,19 +109,6 @@ public function leaveNode(Node $node): void
continue;
}

if ($isOnFunctionSignature
&& $methodNode = $node->getAttribute(ReflectionVisitor::FUNCTION_SCOPE_KEY)
) {
/** @var Node\Stmt\ClassMethod|Node\Expr\Closure $methodNode */
if ($methodNode instanceof Node\Stmt\ClassMethod && $methodNode->isAbstract()) {
continue;
}

if ($methodNode instanceof Node\Stmt\ClassMethod && $methodNode->getAttribute(ParentConnectorVisitor::PARENT_KEY) instanceof Node\Stmt\Interface_) {
continue;
}
}

$isCoveredByTest = $this->isCoveredByTest($isOnFunctionSignature, $node);

if ($this->onlyCovered && !$isCoveredByTest) {
Expand Down
Expand Up @@ -33,32 +33,25 @@

declare(strict_types=1);

namespace Infection\Mutator\Util;
namespace Infection\Visitor;

use Infection\Visitor\ParentConnectorVisitor;
use PhpParser\Node;
use PhpParser\NodeTraverser;
use PhpParser\NodeVisitorAbstract;

/**
* Checks if given node belongs to Interface
*
* @internal
*
* @author Volodimir Melko <v.melko28@gmail.com>
*/
trait InterfaceParentTrait
final class NotMutableIgnoreVisitor extends NodeVisitorAbstract
{
private function isBelongsToInterface(Node $node): bool
public function enterNode(Node $node)
{
$parentNode = $node->getAttribute(ParentConnectorVisitor::PARENT_KEY);

if ($parentNode instanceof Node\Stmt\Interface_) {
return true;
if ($node instanceof Node\Stmt\Interface_) {
return NodeTraverser::DONT_TRAVERSE_CURRENT_AND_CHILDREN;
}

if ($parentNode instanceof Node) {
return $this->isBelongsToInterface($parentNode);
if ($node instanceof Node\Stmt\ClassMethod && $node->isAbstract()) {
return NodeTraverser::DONT_TRAVERSE_CURRENT_AND_CHILDREN;
}

return false;
}
}
2 changes: 2 additions & 0 deletions tests/Mutator/AbstractMutatorTestCase.php
Expand Up @@ -42,6 +42,7 @@
use Infection\Tests\Fixtures\SimpleMutatorVisitor;
use Infection\Visitor\CloneVisitor;
use Infection\Visitor\FullyQualifiedClassNameVisitor;
use Infection\Visitor\NotMutableIgnoreVisitor;
use Infection\Visitor\ParentConnectorVisitor;
use Infection\Visitor\ReflectionVisitor;
use PhpParser\Lexer;
Expand Down Expand Up @@ -152,6 +153,7 @@ private function getMutationsFromCode(string $code, Parser $parser, array $setti

$mutationsCollectorVisitor = new SimpleMutationsCollectorVisitor($this->getMutator($settings), $initialStatements);

$traverser->addVisitor(new NotMutableIgnoreVisitor());
$traverser->addVisitor($mutationsCollectorVisitor);
$traverser->addVisitor(new ParentConnectorVisitor());
$traverser->addVisitor(new FullyQualifiedClassNameVisitor());
Expand Down
130 changes: 130 additions & 0 deletions tests/Visitor/NotMutableIgnoreVisitorTest.php
@@ -0,0 +1,130 @@
<?php
/**
* This code is licensed under the BSD 3-Clause License.
*
* Copyright (c) 2017-2019, Maks Rafalko
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

declare(strict_types=1);

namespace Infection\Tests\Visitor;

use Infection\Visitor\NotMutableIgnoreVisitor;
use PhpParser\Node;
use PhpParser\NodeTraverser;
use PhpParser\NodeVisitorAbstract;

/**
* @internal
*/
final class NotMutableIgnoreVisitorTest extends AbstractBaseVisitorTest
{
private $spyVisitor;

protected function setUp(): void
{
$this->spyVisitor = $this->getSpyVisitor();
}

public function test_it_does_not_traverse_interface_methods(): void
{
$code = <<<'PHP'
<?php
interface Foo
{
public function foo(): array;
public function bar(int $number): string;
}
PHP;
$this->parseAndTraverse($code);
$this->assertSame(0, $this->spyVisitor->getNumberOfClassMethodsVisited());
}

public function test_it_does_not_traverse_abstract_methods(): void
{
$code = <<<'PHP'
<?php
abstract class Foo
{
abstract public function foo(): array;
abstract public function bar(int $number): string;
}
PHP;
$this->parseAndTraverse($code);
$this->assertSame(0, $this->spyVisitor->getNumberOfClassMethodsVisited());
}

public function test_it_still_traverses_normal_methods_in_abstract_classes(): void
{
$code = <<<'PHP'
<?php
abstract class Foo
{
abstract public function foo(): array;
public function bar(int $number): string { return ''; }
}
PHP;
$this->parseAndTraverse($code);
$this->assertSame(1, $this->spyVisitor->getNumberOfClassMethodsVisited());
}

private function getSpyVisitor()
{
return new class() extends NodeVisitorAbstract {
private $nodesVisitedCount = 0;

public function leaveNode(Node $node): void
{
if ($node instanceof Node\Stmt\ClassMethod) {
++$this->nodesVisitedCount;
}
}

public function getNumberOfClassMethodsVisited(): int
{
return $this->nodesVisitedCount;
}
};
}

private function parseAndTraverse(string $code): void
{
$nodes = $this->getNodes($code);

$traverser = new NodeTraverser();

$traverser->addVisitor(new NotMutableIgnoreVisitor());
$traverser->addVisitor($this->spyVisitor);

$traverser->traverse($nodes);
}
}

0 comments on commit 5b02e8e

Please sign in to comment.