File Coverage

blib/lib/Perl/Critic/Policy/BuiltinFunctions/ProhibitBooleanGrep.pm
Criterion Covered Total %
statement 24 48 50.0
branch 3 26 11.5
condition 0 12 0.0
subroutine 11 14 78.5
pod 4 5 80.0
total 42 105 40.0


line stmt bran cond sub pod time code
1             package Perl::Critic::Policy::BuiltinFunctions::ProhibitBooleanGrep;
2              
3 40     40   27456 use 5.010001;
  40         204  
4 40     40   296 use strict;
  40         143  
  40         852  
5 40     40   254 use warnings;
  40         125  
  40         927  
6 40     40   252 use Readonly;
  40         137  
  40         1950  
7              
8 40     40   300 use Perl::Critic::Utils qw{ :severities :classification hashify };
  40         169  
  40         2122  
9 40     40   14854 use parent 'Perl::Critic::Policy';
  40         137  
  40         287  
10              
11             our $VERSION = '1.150';
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 89     89 0 1735 sub supported_parameters { return () }
24 74     74 1 345 sub default_severity { return $SEVERITY_LOW }
25 87     87 1 403 sub default_themes { return qw( core pbp performance certrec ) }
26 30     30 1 104 sub applies_to { return 'PPI::Token::Word' }
27              
28             #-----------------------------------------------------------------------------
29              
30             sub violates {
31 329     329 1 502 my ( $self, $elem, undef ) = @_;
32              
33 329 100       825 return if $elem->content() ne 'grep';
34 3 50       22 return if not is_function_call($elem);
35 0 0         return if not _is_in_boolean_context($elem);
36              
37 0           return $self->violation( $DESC, $EXPL, $elem );
38             }
39              
40             #-----------------------------------------------------------------------------
41              
42             sub _is_in_boolean_context {
43 0     0     my ($token) = @_;
44              
45 0   0       return _does_prev_sibling_cause_boolean($token) || _does_parent_cause_boolean($token);
46             }
47              
48             sub _does_prev_sibling_cause_boolean {
49 0     0     my ($token) = @_;
50              
51 0           my $prev = $token->sprevious_sibling;
52 0 0         return if !$prev;
53 0 0 0       return 1 if $prev->isa('PPI::Token::Word') and $POSTFIX_CONDITIONALS{$prev};
54 0 0 0       return if not ($prev->isa('PPI::Token::Operator') and $BOOLEAN_OPERATORS{$prev});
55 0           my $next = $token->snext_sibling;
56 0 0         return 1 if not $next; # bizarre: grep with no arguments
57              
58             # loose heuristic: unparenthesized grep has no following non-boolean operators
59 0 0         return 1 if not $next->isa('PPI::Structure::List');
60              
61 0           $next = $next->snext_sibling;
62 0 0         return 1 if not $next;
63 0 0 0       return 1 if $next->isa('PPI::Token::Operator') and $BOOLEAN_OPERATORS{$next};
64 0           return;
65             }
66              
67             sub _does_parent_cause_boolean {
68 0     0     my ($token) = @_;
69              
70 0           my $prev = $token->sprevious_sibling;
71 0 0         return if $prev;
72 0           my $parent = $token->statement->parent;
73 0           for (my $node = $parent; $node; $node = $node->parent) { ## no critic (CStyleForLoop)
74 0 0         next if $node->isa('PPI::Structure::List');
75 0 0         return 1 if $node->isa('PPI::Structure::Condition');
76             }
77              
78 0           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::Util>, 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::Util|List::Util>, 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 :