Skip to content

Commit

Permalink
Merge pull request #49 from akeneo/list-unused-reqs
Browse files Browse the repository at this point in the history
Add list-unused-requirements command
  • Loading branch information
phaseinducer authored Dec 5, 2019
2 parents d7c2a17 + f716011 commit c0ff14b
Show file tree
Hide file tree
Showing 11 changed files with 434 additions and 111 deletions.
16 changes: 16 additions & 0 deletions doc/LIST_UNUSED_REQUIREMENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# List unused requirements

The _list-unused-requirements_ command is a complementary tool to the _detect_ command that allows you to easily clean
your configuration. It lists the requirements that are not necessary anymore for each rule :

```bash
php bin/php-coupling-detector list-unused-requirements
```

Like the _detect_ command, you can specify the path to the configuration file with the ``--config-file`` option.

The exit status of the _list-unused-requirements_ command can be: ``10`` if some unused requirements have been found,
or ``0`` otherwise.

Please note that for the moment this is relevant only for rules of type ``ONLY`` as the notion of "unused requirement"
in a ``DISCOURAGED`` or ``FORBIDDEN`` rule makes no sense.
81 changes: 81 additions & 0 deletions spec/Domain/RuleSpec.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
<?php

namespace spec\Akeneo\CouplingDetector\Domain;

use Akeneo\CouplingDetector\Domain\Node;
use Akeneo\CouplingDetector\Domain\NodeInterface;
use Akeneo\CouplingDetector\Domain\Rule;
use Akeneo\CouplingDetector\Domain\RuleInterface;
use PhpSpec\ObjectBehavior;

class RuleSpec extends ObjectBehavior
{
function let()
{
$this->beConstructedWith(
'Namespace\To\Check',
[
'Authorized\Namespace\One',
'Authorized\Namespace\Two',
'Authorized\Namespace\Three',
'Authorized\Namespace\Four',
],
RuleInterface::TYPE_ONLY
);
}

function it_is_initializable()
{
$this->shouldBeAnInstanceOf(Rule::class);
}

function it_matches_a_node(NodeInterface $node)
{
$node->getSubject()->willReturn('Namespace\To\Check\SomeClass');
$this->matches($node)->shouldReturn(true);

$node->getSubject()->willReturn('Another\Namespace\SomeClass');
$this->matches($node)->shouldReturn(false);
}

function it_finds_which_of_its_requirements_are_unused_in_a_set_of_nodes()
{
// We only test this case for Rules of type "ONLY". It is not applicable for other types.
// The fact that this method has a very different behavior depending on an immutable property (the type) and not
// on the inputs probably shows that the class should be split (one class per type).
$this->getUnusedRequirements(
[
new Node(
[
'Authorized\Namespace\One',
'Authorized\Namespace\Three',
],
'Namespace\To\Check\ClassOne',
'/path/to/file',
RuleInterface::TYPE_ONLY
),
new Node(
[
'Authorized\Namespace\One',
],
'Namespace\To\Check\ClassTwo',
'/path/to/file',
RuleInterface::TYPE_ONLY
),
new Node(
[
'Authorized\Namespace\Two',
],
'Namespace\Not\To\Check\ClassThree',
'/path/to/file',
RuleInterface::TYPE_ONLY
),
]
)->shouldReturn(
[
1 => 'Authorized\Namespace\Two',
3 => 'Authorized\Namespace\Four',
]
);
}
}
1 change: 0 additions & 1 deletion spec/NodeParser/NodeParserResolverSpec.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
namespace spec\Akeneo\CouplingDetector\NodeParser;

use PhpSpec\ObjectBehavior;
use Prophecy\Argument;

class NodeParserResolverSpec extends ObjectBehavior
{
Expand Down
1 change: 0 additions & 1 deletion spec/NodeParser/PhpClass/UseDeclarationsExtractorSpec.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
namespace spec\Akeneo\CouplingDetector\NodeParser\PhpClass;

use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
use PhpCsFixer\Tokenizer\Tokens;

class UseDeclarationsExtractorSpec extends ObjectBehavior
Expand Down
191 changes: 101 additions & 90 deletions spec/RuleCheckerSpec.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,125 +2,136 @@

namespace spec\Akeneo\CouplingDetector;

use Akeneo\CouplingDetector\Domain\Node;
use Akeneo\CouplingDetector\Domain\NodeInterface;
use Akeneo\CouplingDetector\Domain\Rule;
use Akeneo\CouplingDetector\Domain\RuleInterface;
use Akeneo\CouplingDetector\Domain\Violation;
use Akeneo\CouplingDetector\Domain\ViolationInterface;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;

class RuleCheckerSpec extends ObjectBehavior
{
function it_matches_a_node(RuleInterface $rule, NodeInterface $node)
function it_checks_a_valid_node_with_forbidden_rule()
{
$rule->getSubject()->willReturn('foo\bar');
$node->getSubject()->willReturn('foo\bar\baz');
$this->match($rule, $node)->shouldReturn(true);
$rule = new Rule(
'foo\bar',
['blu', 'bla', 'bli'],
RuleInterface::TYPE_FORBIDDEN
);

$node = new Node(
['blo', 'bly'],
'foo\bar\baz',
'/path/to/file',
NodeInterface::TYPE_PHP_USE
);

$rule->getSubject()->willReturn('foo\bar');
$node->getSubject()->willReturn('blu\bla\blo');
$this->match($rule, $node)->shouldReturn(false);
$this->check($rule, $node)->shouldReturn(null);
}

function it_checks_a_valid_node_with_forbidden_rule(RuleInterface $rule, NodeInterface $node)
function it_checks_an_invalid_node_with_forbidden_rule()
{
$rule->getSubject()->willReturn('foo\bar');
$rule->getRequirements()->willReturn(array('blu', 'bla', 'bli'));
$rule->getType()->willReturn(RuleInterface::TYPE_FORBIDDEN);
$node->getSubject()->willReturn('foo\bar\baz');
$node->getTokens()->willReturn(array('blo', 'bly'));
$rule = new Rule(
'foo\bar',
['blu', 'bla', 'bli'],
RuleInterface::TYPE_FORBIDDEN
);

$this->check($rule, $node)->shouldReturn(null);
}
$node = new Node(
['blu', 'bla', 'blo', 'bly'],
'foo\bar\baz',
'/path/to/file',
NodeInterface::TYPE_PHP_USE
);

function it_checks_an_invalid_node_with_forbidden_rule(
RuleInterface $rule,
NodeInterface $node,
ViolationInterface $violation
) {
$rule->getSubject()->willReturn('foo\bar');
$rule->getRequirements()->willReturn(array('blu', 'bla', 'bli'));
$rule->getType()->willReturn(RuleInterface::TYPE_FORBIDDEN);
$node->getSubject()->willReturn('foo\bar\baz');
$node->getTokens()->willReturn(array('blu', 'bla', 'blo', 'bly'));

$violation->getNode()->willReturn($node);
$violation->getRule()->willReturn($rule);
$violation->getType()->willReturn(ViolationInterface::TYPE_ERROR);
$violation->getTokenViolations()->willReturn(array('blu', 'bla'));

$this->check($rule, $node)->shouldBeLikeExpectedViolation($violation);
$this->check($rule, $node)->shouldBeLike(new Violation(
$node,
$rule,
['blu', 'bla'],
ViolationInterface::TYPE_ERROR
));
}

function it_checks_a_valid_node_with_discouraged_rule(RuleInterface $rule, NodeInterface $node)
function it_checks_a_valid_node_with_discouraged_rule()
{
$rule->getSubject()->willReturn('foo\bar');
$rule->getRequirements()->willReturn(array('blu', 'bla', 'bli'));
$rule->getType()->willReturn(RuleInterface::TYPE_DISCOURAGED);
$node->getSubject()->willReturn('foo\bar\baz');
$node->getTokens()->willReturn(array('blo', 'bly'));
$rule = new Rule(
'foo\bar',
['blu', 'bla', 'bli'],
RuleInterface::TYPE_DISCOURAGED
);

$node = new Node(
['blo', 'bly'],
'foo\bar\baz',
'/path/to/file',
NodeInterface::TYPE_PHP_USE
);

$this->check($rule, $node)->shouldReturn(null);
}

function it_checks_an_invalid_node_with_discouraged_rule(
RuleInterface $rule,
NodeInterface $node,
ViolationInterface $violation
) {
$rule->getSubject()->willReturn('foo\bar');
$rule->getRequirements()->willReturn(array('blu', 'bla', 'bli'));
$rule->getType()->willReturn(RuleInterface::TYPE_DISCOURAGED);
$node->getSubject()->willReturn('foo\bar\baz');
$node->getTokens()->willReturn(array('blu', 'bla', 'blo', 'bly'));

$violation->getNode()->willReturn($node);
$violation->getRule()->willReturn($rule);
$violation->getType()->willReturn(ViolationInterface::TYPE_WARNING);
$violation->getTokenViolations()->willReturn(array('blu', 'bla'));

$this->check($rule, $node)->shouldBeLikeExpectedViolation($violation);
function it_checks_an_invalid_node_with_discouraged_rule()
{
$rule = new Rule(
'foo\bar',
['blu', 'bla', 'bli'],
RuleInterface::TYPE_DISCOURAGED
);

$node = new Node(
['blu', 'bla', 'blo', 'bly'],
'foo\bar\baz',
'/path/to/file',
NodeInterface::TYPE_PHP_USE
);

$this->check($rule, $node)->shouldBeLike(new Violation(
$node,
$rule,
['blu', 'bla'],
ViolationInterface::TYPE_WARNING
));
}

function it_checks_a_valid_node_with_only_rule(RuleInterface $rule, NodeInterface $node)
function it_checks_a_valid_node_with_only_rule()
{
$rule->getSubject()->willReturn('foo\bar');
$rule->getRequirements()->willReturn(array('blu', 'bla', 'bli'));
$rule->getType()->willReturn(RuleInterface::TYPE_ONLY);
$node->getSubject()->willReturn('foo\bar\baz');
$node->getTokens()->willReturn(array('blu', 'bla'));
$rule = new Rule(
'foo\bar',
['blu', 'bla', 'bli'],
RuleInterface::TYPE_ONLY
);

$this->check($rule, $node)->shouldReturn(null);
}
$node = new Node(
['blu', 'bla'],
'foo\bar\baz',
'/path/to/file',
NodeInterface::TYPE_PHP_USE
);

function it_checks_an_invalid_node_with_only_rule(
RuleInterface $rule,
NodeInterface $node,
ViolationInterface $violation
) {
$rule->getSubject()->willReturn('foo\bar');
$rule->getRequirements()->willReturn(array('blu', 'bla', 'bli'));
$rule->getType()->willReturn(RuleInterface::TYPE_ONLY);
$node->getSubject()->willReturn('foo\bar\baz');
$node->getTokens()->willReturn(array('blu', 'bla', 'blo', 'bly'));

$violation->getNode()->willReturn($node);
$violation->getRule()->willReturn($rule);
$violation->getType()->willReturn(ViolationInterface::TYPE_ERROR);
$violation->getTokenViolations()->willReturn(array('blo', 'bly'));

$this->check($rule, $node)->shouldBeLikeExpectedViolation($violation);
$this->check($rule, $node)->shouldReturn(null);
}

public function getMatchers(): array
function it_checks_an_invalid_node_with_only_rule()
{
return array(
'beLikeExpectedViolation' => function ($subject, $expected) {
return
$subject->getNode() === $expected->getNode() &&
$subject->getRule() === $expected->getRule() &&
$subject->getTokenViolations() === $expected->getTokenViolations() &&
$subject->getType() === $expected->getType();
},
$rule = new Rule(
'foo\bar',
['blu', 'bla', 'bli'],
RuleInterface::TYPE_ONLY
);

$node = new Node(
['blu', 'bla', 'blo', 'bly'],
'foo\bar\baz',
'/path/to/file',
NodeInterface::TYPE_PHP_USE
);

$this->check($rule, $node)->shouldBeLike(new Violation(
$node,
$rule,
['blo', 'bly'],
ViolationInterface::TYPE_ERROR
));
}
}
2 changes: 2 additions & 0 deletions src/Console/Application.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace Akeneo\CouplingDetector\Console;

use Akeneo\CouplingDetector\Console\Command\DetectCommand;
use Akeneo\CouplingDetector\Console\Command\ListUnusedRequirementsCommand;
use Akeneo\CouplingDetector\CouplingDetector;
use Symfony\Component\Console\Application as BaseApplication;

Expand All @@ -23,5 +24,6 @@ public function __construct()
error_reporting(-1);
parent::__construct('Akeneo coupling detector', CouplingDetector::VERSION);
$this->add(new DetectCommand());
$this->add(new ListUnusedRequirementsCommand());
}
}
5 changes: 3 additions & 2 deletions src/Console/Command/DetectCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -151,8 +151,9 @@ private function loadConfiguration($filePath)
if (!$config instanceof Configuration) {
throw new \InvalidArgumentException(
sprintf(
'The configuration file "%s" must return a "Akeneo\CouplingDetector\Configuration\Configuration"',
$filePath
'The configuration file "%s" must return a "%s" instance.',
$filePath,
Configuration::class
)
);
}
Expand Down
Loading

0 comments on commit c0ff14b

Please sign in to comment.