Skip to content

Commit

Permalink
Change the calculation of cyclomatic complexity
Browse files Browse the repository at this point in the history
 - xor has not been covered yet.
 - The ternary operator and the coalesce operator has been count both
   as 2. Also the default case of a switch statement has been counted.
   But as the else statement is not counted because it represents the
   default path (and can be elimated), the default case should not be
   counted (corresponds to else) and both the ternary and the coalesce
   operator should be counted as 1, because they can be exchanged by
   if-else / if which would be counted as 1.
   The spaceship operator by contrast is now counted as 2, because it
   would need if-elseif-else to replace it.
 - continue does not increase the cyclomatic complexity as its no
   decision. It's the if around the continue which increases the ccn.
  • Loading branch information
UFOMelkor committed May 24, 2018
1 parent 285b1f8 commit b422201
Show file tree
Hide file tree
Showing 3 changed files with 95 additions and 23 deletions.
33 changes: 14 additions & 19 deletions src/Hal/Metric/Class_/Complexity/CyclomaticComplexityVisitor.php
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
<?php
namespace Hal\Metric\Class_\Complexity;

use Hal\Component\Reflected\Method;
use Hal\Metric\Helper\MetricClassNameGenerator;
use Hal\Metric\Metrics;
use PhpParser\Node;
Expand All @@ -24,31 +23,22 @@
*/
class CyclomaticComplexityVisitor extends NodeVisitorAbstract
{

/**
* @var Metrics
*/
private $metrics;

/**
* ClassEnumVisitor constructor.
* @param Metrics $metrics
*/
public function __construct(Metrics $metrics)
{
$this->metrics = $metrics;
}

/**
* @inheritdoc
*/
public function leaveNode(Node $node)
{
if ($node instanceof Stmt\Class_
|| $node instanceof Stmt\Interface_
|| $node instanceof Stmt\Trait_
) {

$class = $this->metrics->get(MetricClassNameGenerator::getName($node));

$ccn = 1;
Expand All @@ -62,9 +52,9 @@ public function leaveNode(Node $node)
$ccn = 0;

foreach (get_object_vars($node) as $name => $member) {
foreach (is_array($member) ? $member : [$member] as $member_item) {
if ($member_item instanceof Node) {
$ccn += $cb($member_item);
foreach (is_array($member) ? $member : [$member] as $memberItem) {
if ($memberItem instanceof Node) {
$ccn += $cb($memberItem);
}
}
}
Expand All @@ -78,18 +68,23 @@ public function leaveNode(Node $node)
case $node instanceof Stmt\Do_:
case $node instanceof Node\Expr\BinaryOp\LogicalAnd:
case $node instanceof Node\Expr\BinaryOp\LogicalOr:
case $node instanceof Node\Expr\BinaryOp\LogicalXor:
case $node instanceof Node\Expr\BinaryOp\BooleanAnd:
case $node instanceof Node\Expr\BinaryOp\BooleanOr:
case $node instanceof Node\Expr\BinaryOp\Spaceship:
case $node instanceof Stmt\Case_: // include default
case $node instanceof Stmt\Catch_:
case $node instanceof Stmt\Continue_:
$ccn++;
break;
case $node instanceof Node\Expr\Ternary:
case $node instanceof Node\Expr\BinaryOp\Coalesce:
$ccn = $ccn + 2;
$ccn++;
break;
case $node instanceof Stmt\Case_: // include default
if ($node->cond !== null) { // exclude default
$ccn++;
}
break;
case $node instanceof Node\Expr\BinaryOp\Spaceship:
$ccn += 2;
break;

}
return $ccn;
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,17 +53,22 @@ public function testCyclomaticComplexityOfMethodsIsWellCalculated($example, $cla
public function provideExamplesForClasses()
{
return [
[ __DIR__.'/../../examples/cyclomatic1.php', 'A', 8],
[ __DIR__.'/../../examples/cyclomatic1.php', 'B', 5],
[ __DIR__.'/../../examples/cyclomatic_anon.php', 'Foo\C', 1],
'A' => [ __DIR__.'/../../examples/cyclomatic1.php', 'A', 8],
'B' => [ __DIR__.'/../../examples/cyclomatic1.php', 'B', 4],
'Foo\\C' => [ __DIR__.'/../../examples/cyclomatic_anon.php', 'Foo\\C', 1],
'SwitchCase' => [ __DIR__.'/../../examples/cyclomatic_full.php', 'SwitchCase', 4],
'IfElseif' => [ __DIR__.'/../../examples/cyclomatic_full.php', 'IfElseif', 7],
'Loops' => [ __DIR__.'/../../examples/cyclomatic_full.php', 'Loops', 5],
'CatchIt' => [ __DIR__.'/../../examples/cyclomatic_full.php', 'CatchIt', 3],
'Logical' => [ __DIR__.'/../../examples/cyclomatic_full.php', 'Logical', 11],
];
}

public function provideExamplesForMethods()
{
return [
[ __DIR__.'/../../examples/cyclomatic1.php', 'A', 6],
[ __DIR__.'/../../examples/cyclomatic1.php', 'B', 5],
[ __DIR__.'/../../examples/cyclomatic1.php', 'B', 4],
];
}

Expand Down
72 changes: 72 additions & 0 deletions tests/Metric/examples/cyclomatic_full.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
<?php
class SwitchCase // ccn2: 4
{
function __invoke()
{
switch ('abc') {
case 'abc':
case 'def':
case 'hij':
break;
default:
}
}
}

class IfElseif // ccn2: 7
{
function __invoke()
{
if (true) {
if (true) {
} elseif (true) {
} else {
}
} elseif (true) {
if (false) {
}
}

if (true) {
}
}
}

class Loops // ccn2: 5
{
function __invoke()
{
while (true) {
do {
} while (false);
}
foreach (array() as $each) {
for ($i = 0; $i < 0; ++$i) {
}
}
}
}

class CatchIt // ccn2: 3
{
function __invoke()
{
try {
} catch (Exception $e) {
} catch (Throwable $e) {
} finally {
}
}
}

class Logical // ccn2: 11
{
function __invoke()
{
$a = (true || false) and (false && true) or (true xor false);
$b = $a ? 1 : 2;
$c = $b ?: 0;
$d = $b ?? $c;
$e = $b <=> $d;
}
}

0 comments on commit b422201

Please sign in to comment.