File Coverage

blib/lib/Perl/Critic/Utils/McCabe.pm
Criterion Covered Total %
statement 56 56 100.0
branch 12 14 85.7
condition n/a
subroutine 12 12 100.0
pod 2 2 100.0
total 82 84 97.6


line stmt bran cond sub pod time code
1             package Perl::Critic::Utils::McCabe;
2              
3 40     40   673 use 5.010001;
  40         140  
4 40     40   212 use strict;
  40         85  
  40         1209  
5 40     40   238 use warnings;
  40         95  
  40         1132  
6              
7 40     40   221 use Readonly;
  40         90  
  40         2273  
8              
9 40     40   292 use Perl::Critic::Utils qw{ :data_conversion :classification };
  40         123  
  40         2124  
10              
11 40     40   11951 use Exporter 'import';
  40         95  
  40         24612  
12              
13             #-----------------------------------------------------------------------------
14              
15             our $VERSION = '1.148';
16              
17             #-----------------------------------------------------------------------------
18              
19             Readonly::Array our @EXPORT_OK =>
20             qw( calculate_mccabe_of_sub calculate_mccabe_of_main );
21              
22             #-----------------------------------------------------------------------------
23              
24             Readonly::Hash my %LOGIC_OPS =>
25             hashify( qw( && || ||= &&= or and xor ? <<= >>= ) );
26              
27             Readonly::Hash my %LOGIC_KEYWORDS =>
28             hashify( qw( if else elsif unless until while for foreach ) );
29              
30             #-----------------------------------------------------------------------------
31              
32             sub calculate_mccabe_of_sub {
33              
34 644     644 1 1453 my ( $sub ) = @_;
35              
36 644         1162 my $count = 1; # Minimum score is 1
37 644         1783 $count += _count_logic_keywords( $sub );
38 644         2194 $count += _count_logic_operators( $sub );
39              
40 644         2323 return $count;
41             }
42              
43             #-----------------------------------------------------------------------------
44              
45             sub calculate_mccabe_of_main {
46              
47 34     34 1 102 my ( $doc ) = @_;
48              
49 34         89 my $count = 1; # Minimum score is 1
50 34         137 $count += _count_main_logic_operators_and_keywords( $doc );
51 34         109 return $count;
52             }
53              
54             #-----------------------------------------------------------------------------
55              
56             sub _count_main_logic_operators_and_keywords {
57              
58 34     34   120 my ( $doc ) = @_;
59              
60             # I can't leverage Perl::Critic::Document's fast search mechanism here
61             # because we're not searching for elements by class name. So to speed
62             # things up, search for both keywords and operators at the same time.
63              
64             my $wanted = sub {
65              
66 2709     2709   32688 my (undef, $elem) = @_;
67              
68             # Only count things that *are not* in a subroutine. Returning an
69             # explicit 'undef' here prevents PPI from descending into the node.
70              
71             ## no critic (ProhibitExplicitReturnUndef)
72 2709 100       7390 return undef if $elem->isa('PPI::Statement::Sub');
73              
74              
75 2702 100       11506 if ( $elem->isa('PPI::Token::Word') ) {
    100          
76 338 50       906 return 0 if is_hash_key( $elem );
77 338         888 return exists $LOGIC_KEYWORDS{$elem};
78             }
79             elsif ($elem->isa('PPI::Token::Operator') ) {
80 151         426 return exists $LOGIC_OPS{$elem};
81             }
82 34         301 };
83              
84 34         140 my $logic_operators_and_keywords = $doc->find( $wanted );
85              
86             my $count = $logic_operators_and_keywords ?
87 34 100       564 scalar @{$logic_operators_and_keywords} : 0;
  15         41  
88              
89 34         239 return $count;
90             }
91              
92             #-----------------------------------------------------------------------------
93              
94             sub _count_logic_keywords {
95              
96 644     644   1455 my ( $sub ) = @_;
97 644         1155 my $count = 0;
98              
99             # Here, I'm using this round-about method of finding elements so
100             # that I can take advantage of Perl::Critic::Document's faster
101             # find() mechanism. It can only search for elements by class name.
102              
103 644         2207 my $keywords_ref = $sub->find('PPI::Token::Word');
104 644 50       490297 if ( $keywords_ref ) { # should always be true due to "sub" keyword
105 644         1407 my @filtered = grep { ! is_hash_key($_) } @{ $keywords_ref };
  2378         6339  
  644         1754  
106 644         2107 $count = grep { exists $LOGIC_KEYWORDS{$_} } @filtered;
  2355         23336  
107             }
108 644         8886 return $count;
109             }
110              
111             #-----------------------------------------------------------------------------
112              
113             sub _count_logic_operators {
114              
115 644     644   1824 my ( $sub ) = @_;
116 644         1246 my $count = 0;
117              
118             # Here, I'm using this round-about method of finding elements so
119             # that I can take advantage of Perl::Critic::Document's faster
120             # find() mechanism. It can only search for elements by class name.
121              
122 644         1900 my $operators_ref = $sub->find('PPI::Token::Operator');
123 644 100       451253 if ( $operators_ref ) {
124 156         450 $count = grep { exists $LOGIC_OPS{$_} } @{ $operators_ref };
  429         3705  
  156         575  
125             }
126              
127 644         3592 return $count;
128             }
129              
130              
131             1;
132              
133             __END__
134              
135             #-----------------------------------------------------------------------------
136              
137             =pod
138              
139             =for stopwords McCabe
140              
141             =head1 NAME
142              
143             Perl::Critic::Utils::McCabe - Functions that calculate the McCabe score of source code.
144              
145              
146             =head1 DESCRIPTION
147              
148             Provides approximations of McCabe scores. The McCabe score of a set
149             of code describes the number of possible paths through it. The
150             functions here approximate the McCabe score by summing the number of
151             conditional statements and operators within a set of code. See
152             L<http://en.wikipedia.org/wiki/Cyclomatic_complexity> for
153             some discussion about the McCabe number and other complexity metrics.
154              
155              
156             =head1 INTERFACE SUPPORT
157              
158             This is considered to be a public module. Any changes to its
159             interface will go through a deprecation cycle.
160              
161              
162             =head1 IMPORTABLE SUBS
163              
164             =over
165              
166             =item C<calculate_mccabe_of_sub( $sub )>
167              
168             Calculates an approximation of the McCabe number of the code in a
169             L<PPI::Statement::Sub|PPI::Statement::Sub>.
170              
171              
172             =item C<calculate_mccabe_of_main( $doc )>
173              
174             Calculates an approximation of the McCabe number of all the code in a
175             L<PPI::Statement::Document|PPI::Statement::Document> that is B<not>
176             contained in a subroutine.
177              
178             =back
179              
180              
181             =head1 AUTHOR
182              
183             Jeffrey Ryan Thalhammer <jeff@imaginative-software.com>
184              
185              
186             =head1 COPYRIGHT
187              
188             Copyright (c) 2005-2011 Imaginative Software Systems. All rights reserved.
189              
190             This program is free software; you can redistribute it and/or modify
191             it under the same terms as Perl itself. The full text of this license
192             can be found in the LICENSE file included with this module.
193              
194             =cut
195              
196             # Local Variables:
197             # mode: cperl
198             # cperl-indent-level: 4
199             # fill-column: 78
200             # indent-tabs-mode: nil
201             # c-indentation-style: bsd
202             # End:
203             # ex: set ts=8 sts=4 sw=4 tw=78 ft=perl expandtab shiftround :