File Coverage

blib/lib/Perl/Critic/Policy/BuiltinFunctions/ProhibitBooleanGrep.pm
Criterion Covered Total %
statement 48 48 100.0
branch 26 26 100.0
condition 12 12 100.0
subroutine 14 14 100.0
pod 4 5 80.0
total 104 105 99.0


line stmt bran cond sub pod time code
1             package Perl::Critic::Policy::BuiltinFunctions::ProhibitBooleanGrep;
2              
3 40     40   29090 use 5.010001;
  40         201  
4 40     40   311 use strict;
  40         128  
  40         879  
5 40     40   256 use warnings;
  40         141  
  40         988  
6 40     40   252 use Readonly;
  40         133  
  40         2078  
7              
8 40     40   316 use Perl::Critic::Utils qw{ :severities :classification hashify };
  40         117  
  40         2272  
9 40     40   14763 use parent 'Perl::Critic::Policy';
  40         118  
  40         277  
10              
11             our $VERSION = '1.146';
12              
13             #-----------------------------------------------------------------------------
14              
15             Readonly::Scalar my $DESC => q{"grep" used in boolean context};
16             Readonly::Scalar my $EXPL => [71,72];
17              
18             Readonly::Hash my %POSTFIX_CONDITIONALS => hashify( qw(if unless while until) );
19             Readonly::Hash my %BOOLEAN_OPERATORS => hashify( qw(&& || ! not or and));
20              
21             #-----------------------------------------------------------------------------
22              
23 103     103 0 1836 sub supported_parameters { return () }
24 86     86 1 423 sub default_severity { return $SEVERITY_LOW }
25 87     87 1 448 sub default_themes { return qw( core pbp performance certrec ) }
26 44     44 1 165 sub applies_to { return 'PPI::Token::Word' }
27              
28             #-----------------------------------------------------------------------------
29              
30             sub violates {
31 388     388 1 739 my ( $self, $elem, undef ) = @_;
32              
33 388 100       850 return if $elem->content() ne 'grep';
34 28 100       178 return if not is_function_call($elem);
35 24 100       58 return if not _is_in_boolean_context($elem);
36              
37 12         129 return $self->violation( $DESC, $EXPL, $elem );
38             }
39              
40             #-----------------------------------------------------------------------------
41              
42             sub _is_in_boolean_context {
43 24     24   54 my ($token) = @_;
44              
45 24   100     52 return _does_prev_sibling_cause_boolean($token) || _does_parent_cause_boolean($token);
46             }
47              
48             sub _does_prev_sibling_cause_boolean {
49 24     24   41 my ($token) = @_;
50              
51 24         51 my $prev = $token->sprevious_sibling;
52 24 100       527 return if !$prev;
53 14 100 100     60 return 1 if $prev->isa('PPI::Token::Word') and $POSTFIX_CONDITIONALS{$prev};
54 10 100 100     65 return if not ($prev->isa('PPI::Token::Operator') and $BOOLEAN_OPERATORS{$prev});
55 6         108 my $next = $token->snext_sibling;
56 6 100       138 return 1 if not $next; # bizarre: grep with no arguments
57              
58             # loose heuristic: unparenthesized grep has no following non-boolean operators
59 5 100       19 return 1 if not $next->isa('PPI::Structure::List');
60              
61 4         37 $next = $next->snext_sibling;
62 4 100       97 return 1 if not $next;
63 3 100 100     16 return 1 if $next->isa('PPI::Token::Operator') and $BOOLEAN_OPERATORS{$next};
64 2         23 return;
65             }
66              
67             sub _does_parent_cause_boolean {
68 16     16   93 my ($token) = @_;
69              
70 16         41 my $prev = $token->sprevious_sibling;
71 16 100       389 return if $prev;
72 10         40 my $parent = $token->statement->parent;
73 10         174 for (my $node = $parent; $node; $node = $node->parent) { ## no critic (CStyleForLoop)
74 22 100       134 next if $node->isa('PPI::Structure::List');
75 18 100       89 return 1 if $node->isa('PPI::Structure::Condition');
76             }
77              
78 6         50 return;
79             }
80              
81             1;
82              
83             __END__
84              
85             #-----------------------------------------------------------------------------
86              
87             =pod
88              
89             =head1 NAME
90              
91             Perl::Critic::Policy::BuiltinFunctions::ProhibitBooleanGrep - Use C<any> from C<List::SomeUtils> or C<List::MoreUtils> instead of C<grep> in boolean context.
92              
93              
94             =head1 AFFILIATION
95              
96             This Policy is part of the core L<Perl::Critic|Perl::Critic>
97             distribution.
98              
99              
100             =head1 DESCRIPTION
101              
102             Using C<grep> in boolean context is a common idiom for checking if any
103             elements in a list match a condition. This works because boolean
104             context is a subset of scalar context, and grep returns the number of
105             matches in scalar context. A non-zero number of matches means a
106             match.
107              
108             But consider the case of a long array where the first element is a
109             match. Boolean C<grep> still checks all of the rest of the elements
110             needlessly. Instead, a better solution is to use the C<any> function
111             from either L<List::SomeUtils|List::SomeUtils> or
112             L<List::MoreUtils|List::MoreUtils>. The C<any> function will return as soon
113             as a successful match is found, rather than processing the entire list.
114             This saves time.
115              
116              
117             =head1 CONFIGURATION
118              
119             This Policy is not configurable except for the standard options.
120              
121              
122             =head1 CAVEATS
123              
124             The algorithm for detecting boolean context takes a LOT of shortcuts.
125             There are lots of known false negatives. But, I was conservative in
126             writing this, so I hope there are no false positives.
127              
128              
129             =head1 AUTHOR
130              
131             Chris Dolan <cdolan@cpan.org>
132              
133              
134             =head1 CREDITS
135              
136             Initial development of this policy was supported by a grant from the
137             Perl Foundation.
138              
139              
140             =head1 COPYRIGHT
141              
142             Copyright (c) 2007-2021 Chris Dolan. Many rights reserved.
143              
144             This program is free software; you can redistribute it and/or modify
145             it under the same terms as Perl itself. The full text of this license
146             can be found in the LICENSE file included with this module.
147              
148             =cut
149              
150             # Local Variables:
151             # mode: cperl
152             # cperl-indent-level: 4
153             # fill-column: 78
154             # indent-tabs-mode: nil
155             # c-indentation-style: bsd
156             # End:
157             # ex: set ts=8 sts=4 sw=4 tw=78 ft=perl expandtab shiftround :