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   678 use 5.010001;
  40         152  
4 40     40   207 use strict;
  40         86  
  40         814  
5 40     40   232 use warnings;
  40         82  
  40         1056  
6              
7 40     40   239 use Readonly;
  40         85  
  40         2063  
8              
9 40     40   294 use Perl::Critic::Utils qw{ :data_conversion :classification };
  40         106  
  40         2131  
10              
11 40     40   11706 use Exporter 'import';
  40         90  
  40         23276  
12              
13             #-----------------------------------------------------------------------------
14              
15             our $VERSION = '1.150';
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 15     15 1 38 my ( $sub ) = @_;
35              
36 15         31 my $count = 1; # Minimum score is 1
37 15         48 $count += _count_logic_keywords( $sub );
38 15         75 $count += _count_logic_operators( $sub );
39              
40 15         56 return $count;
41             }
42              
43             #-----------------------------------------------------------------------------
44              
45             sub calculate_mccabe_of_main {
46              
47 30     30 1 67 my ( $doc ) = @_;
48              
49 30         61 my $count = 1; # Minimum score is 1
50 30         97 $count += _count_main_logic_operators_and_keywords( $doc );
51 30         99 return $count;
52             }
53              
54             #-----------------------------------------------------------------------------
55              
56             sub _count_main_logic_operators_and_keywords {
57              
58 30     30   79 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 2412     2412   23314 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 2412 100       5405 return undef if $elem->isa('PPI::Statement::Sub');
73              
74              
75 2407 100       8365 if ( $elem->isa('PPI::Token::Word') ) {
    100          
76 310 50       666 return 0 if is_hash_key( $elem );
77 310         911 return exists $LOGIC_KEYWORDS{$elem};
78             }
79             elsif ($elem->isa('PPI::Token::Operator') ) {
80 132         286 return exists $LOGIC_OPS{$elem};
81             }
82 30         219 };
83              
84 30         111 my $logic_operators_and_keywords = $doc->find( $wanted );
85              
86             my $count = $logic_operators_and_keywords ?
87 30 100       475 scalar @{$logic_operators_and_keywords} : 0;
  12         30  
88              
89 30         171 return $count;
90             }
91              
92             #-----------------------------------------------------------------------------
93              
94             sub _count_logic_keywords {
95              
96 15     15   33 my ( $sub ) = @_;
97 15         25 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 15         94 my $keywords_ref = $sub->find('PPI::Token::Word');
104 15 50       11590 if ( $keywords_ref ) { # should always be true due to "sub" keyword
105 15         53 my @filtered = grep { ! is_hash_key($_) } @{ $keywords_ref };
  58         176  
  15         39  
106 15         87 $count = grep { exists $LOGIC_KEYWORDS{$_} } @filtered;
  58         535  
107             }
108 15         174 return $count;
109             }
110              
111             #-----------------------------------------------------------------------------
112              
113             sub _count_logic_operators {
114              
115 15     15   32 my ( $sub ) = @_;
116 15         27 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 15         50 my $operators_ref = $sub->find('PPI::Token::Operator');
123 15 100       10442 if ( $operators_ref ) {
124 3         8 $count = grep { exists $LOGIC_OPS{$_} } @{ $operators_ref };
  5         33  
  3         11  
125             }
126              
127 15         64 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 :