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   690 use 5.010001;
  40         145  
4 40     40   214 use strict;
  40         107  
  40         1126  
5 40     40   240 use warnings;
  40         125  
  40         1131  
6              
7 40     40   239 use Readonly;
  40         100  
  40         2194  
8              
9 40     40   285 use Perl::Critic::Utils qw{ :data_conversion :classification };
  40         101  
  40         2073  
10              
11 40     40   12371 use Exporter 'import';
  40         99  
  40         24668  
12              
13             #-----------------------------------------------------------------------------
14              
15             our $VERSION = '1.146';
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 1803 my ( $sub ) = @_;
35              
36 644         1616 my $count = 1; # Minimum score is 1
37 644         2286 $count += _count_logic_keywords( $sub );
38 644         1913 $count += _count_logic_operators( $sub );
39              
40 644         2459 return $count;
41             }
42              
43             #-----------------------------------------------------------------------------
44              
45             sub calculate_mccabe_of_main {
46              
47 34     34 1 95 my ( $doc ) = @_;
48              
49 34         85 my $count = 1; # Minimum score is 1
50 34         113 $count += _count_main_logic_operators_and_keywords( $doc );
51 34         114 return $count;
52             }
53              
54             #-----------------------------------------------------------------------------
55              
56             sub _count_main_logic_operators_and_keywords {
57              
58 34     34   95 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   32562 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       7366 return undef if $elem->isa('PPI::Statement::Sub');
73              
74              
75 2702 100       11445 if ( $elem->isa('PPI::Token::Word') ) {
    100          
76 338 50       767 return 0 if is_hash_key( $elem );
77 338         918 return exists $LOGIC_KEYWORDS{$elem};
78             }
79             elsif ($elem->isa('PPI::Token::Operator') ) {
80 151         407 return exists $LOGIC_OPS{$elem};
81             }
82 34         284 };
83              
84 34         133 my $logic_operators_and_keywords = $doc->find( $wanted );
85              
86             my $count = $logic_operators_and_keywords ?
87 34 100       507 scalar @{$logic_operators_and_keywords} : 0;
  15         40  
88              
89 34         218 return $count;
90             }
91              
92             #-----------------------------------------------------------------------------
93              
94             sub _count_logic_keywords {
95              
96 644     644   1521 my ( $sub ) = @_;
97 644         1266 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         2272 my $keywords_ref = $sub->find('PPI::Token::Word');
104 644 50       501032 if ( $keywords_ref ) { # should always be true due to "sub" keyword
105 644         1523 my @filtered = grep { ! is_hash_key($_) } @{ $keywords_ref };
  2378         6398  
  644         1885  
106 644         2267 $count = grep { exists $LOGIC_KEYWORDS{$_} } @filtered;
  2355         24670  
107             }
108 644         8253 return $count;
109             }
110              
111             #-----------------------------------------------------------------------------
112              
113             sub _count_logic_operators {
114              
115 644     644   1560 my ( $sub ) = @_;
116 644         1123 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         1870 my $operators_ref = $sub->find('PPI::Token::Operator');
123 644 100       459668 if ( $operators_ref ) {
124 156         488 $count = grep { exists $LOGIC_OPS{$_} } @{ $operators_ref };
  429         4003  
  156         513  
125             }
126              
127 644         3677 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 :