File Coverage

blib/lib/Perl/Critic/Policy/CognitiveComplexity/ProhibitExcessCognitiveComplexity.pm
Criterion Covered Total %
statement 79 80 98.7
branch 23 24 95.8
condition 15 15 100.0
subroutine 18 19 94.7
pod 4 10 40.0
total 139 148 93.9


line stmt bran cond sub pod time code
1             package Perl::Critic::Policy::CognitiveComplexity::ProhibitExcessCognitiveComplexity;
2 11     11   1504569 use strict;
  11         16  
  11         268  
3 11     11   40 use warnings;
  11         13  
  11         206  
4              
5 11     11   517 use Readonly;
  11         2581  
  11         454  
6 11     11   46 use Readonly qw (Scalar);
  11         12  
  11         454  
7 11     11   586 use Perl::Critic::Utils qw{ :severities :classification :ppi };
  11         82075  
  11         570  
8 11     11   8504 use Perl::Critic::Violation;
  11         141121  
  11         328  
9 11     11   67 use base 'Perl::Critic::Policy';
  11         18  
  11         5096  
10              
11             our $VERSION = '0.03';
12              
13             Scalar my $EXPL => q{Avoid code that is nested, and thus difficult to grasp.};
14             Readonly my %BOOLEAN_OPS => map { $_ => 1 } qw( && || and or );
15              
16             sub supported_parameters {
17                 return ( {
18 10     10 0 67515             name => 'warn_level',
19                         description => 'The complexity score allowed before warning starts.',
20                         default_string => '10',
21                         behavior => 'integer',
22                         integer_minimum => 1,
23                     },
24                     {
25                         name => 'info_level',
26                         description => 'The complexity score allowed before informational reporting starts.',
27                         default_string => '1',
28                         behavior => 'integer',
29                         integer_minimum => 1,
30                     }
31                 );
32             }
33              
34             sub default_severity {
35 1     1 1 14     return $SEVERITY_MEDIUM;
36             }
37              
38             sub default_themes {
39 0     0 1 0     return qw( complexity maintenance );
40             }
41              
42             sub applies_to {
43 10     10 1 79184     return 'PPI::Statement::Sub';
44             }
45              
46             sub violates {
47 10     10 1 179     my ( $self, $elem, undef ) = @_;
48              
49             # only report complexity for named subs.
50 10 50       44     my $name = $elem->name() or return;
51              
52             # start with complexity of 0
53 10         211     my $score = 0;
54 10         56     my $block = $elem->find_first('PPI::Structure::Block');
55              
56 10         2496     $score += $self->structure_score($block , 0);
57 10         36     $score += $self->operator_score($block);
58              
59             # return no violation
60 10 100       38     return if($score < $self->{'_info_level'});
61             # return violation
62 8         36     return ($self->new_violation($elem, $score));
63             }
64              
65             sub new_violation {
66 8     8 0 12     my $self = shift;
67 8         14     my ($elem, $score) = @_;
68 8         21     my $name = $elem->name();
69 8         145     my $desc = qq<Subroutine '$name' with complexity score of '$score'>;
70              
71                 return Perl::Critic::Violation->new( $desc, $EXPL, $elem,
72 8 100       112         ($score >= $self->{'_warn_level'} ? $self->get_severity() : $SEVERITY_LOWEST ));
73             }
74              
75             sub structure_score {
76 227     227 0 176     my $self = shift;
77 227         170     my ( $elem, $nesting ) = @_;
78              
79 227 100       827     return 0 unless ( $elem->can('schildren') );
80              
81 93         79     my $complexity = 0;
82              
83 93         137     for my $child ( $elem->schildren() ) {
84             #my $inc = 0;
85 217 100 100     2348         if ( $child->isa('PPI::Structure::Given')
    100 100        
      100        
86                         || $child->isa('PPI::Structure::Condition')
87                         || $child->isa('PPI::Structure::For')
88                         )
89                     {
90 14 100       98             if($self->nesting_increase($child->parent)) {
91 10         20                 $complexity += $nesting;
92                         } else {
93             # missing compound statement / increment on postfix operator_score
94 4         5                 $complexity += $nesting + 1;
95                         }
96                     }
97             # 'return' is a break-statement, but does not count in terms of cognitive complexity.
98                     elsif ( $child->isa('PPI::Statement::Break') && ! $self->is_return_statement($child)) {
99 1         11             $complexity++;
100                     }
101 217         353         $complexity += $self->structure_score( $child, $nesting + $self->nesting_increase($child) );
102                 }
103 93         145     return $complexity;
104             }
105              
106             sub operator_score {
107 10     10 0 14     my $self = shift;
108 10         14     my ($sub) = @_;
109 10         14     my $by_parent = {};
110 10         59     my $elems = $sub->find('PPI::Token::Operator');
111 10         9112     my $sum = 0;
112 10 100       38     if($elems) {
113 6         147         map { push @{$by_parent->{$_->parent}}, $_->content }
  6         38  
114 7         16             grep { exists $BOOLEAN_OPS{$_->content} } @$elems;
  27         143  
115 7         117         for my $parent (keys %{$by_parent}) {
  7         26  
116 3         4             my @ops = @{$by_parent->{$parent}};
  3         8  
117 3         12             OP: for(my $i = 0; $i < scalar @ops; ++$i) {
118 6 100 100     28                 if($i > 0 && $ops[$i-1] eq $ops[$i]) {
119 1         4                     next OP;
120                             }
121 5         12                 $sum++;
122                         }
123                     }
124                 }
125 10         31     return $sum;
126             }
127              
128             sub is_return_statement {
129 6     6 0 9     my $self = shift;
130 6         5     my ($child) = @_;
131 6     22   25     scalar $child->find( sub { $_[1]->content eq 'return' });
  22         170  
132             }
133              
134             sub nesting_increase {
135 231     231 0 209     my $self = shift;
136 231         169     my ($child) = @_;
137              
138             # if/when/for...
139 231 100       517     return 1 if ($child->isa('PPI::Statement::Compound'));
140 213 100       432     return 1 if ($child->isa('PPI::Statement::Given'));
141             # anonymous sub
142 211 100 100 393   566     return 1 if ($child->isa('PPI::Statement') && $child->find( sub { $_[1]->content eq 'sub' }));
  393         3308  
143              
144 210         716     return 0;
145             }
146              
147             1;
148