File Coverage

blib/lib/Perl/Critic/Policy/ValuesAndExpressions/NotWithCompare.pm
Criterion Covered Total %
statement 130 135 96.3
branch 63 68 92.6
condition 23 29 79.3
subroutine 16 16 100.0
pod 1 1 100.0
total 233 249 93.5


line stmt bran cond sub pod time code
1             # Copyright 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017 Kevin Ryde
2              
3             # This file is part of Perl-Critic-Pulp.
4              
5             # Perl-Critic-Pulp is free software; you can redistribute it and/or modify
6             # it under the terms of the GNU General Public License as published by the
7             # Free Software Foundation; either version 3, or (at your option) any later
8             # version.
9             #
10             # Perl-Critic-Pulp is distributed in the hope that it will be useful, but
11             # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
12             # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
13             # for more details.
14             #
15             # You should have received a copy of the GNU General Public License along
16             # with Perl-Critic-Pulp. If not, see <http://www.gnu.org/licenses/>.
17              
18              
19             package Perl::Critic::Policy::ValuesAndExpressions::NotWithCompare;
20 40     40   1513550 use 5.006;
  40         159  
21 40     40   206 use strict;
  40         92  
  40         887  
22 40     40   369 use warnings;
  40         1264  
  40         1387  
23 40     40   258 use List::Util qw(min max);
  40         94  
  40         3795  
24 40     40   245 use base 'Perl::Critic::Policy';
  40         92  
  40         5707  
25             # 1.100 for precedence_of() supporting -f etc filetests
26 40         2848 use Perl::Critic::Utils 1.100 qw(is_perl_builtin
27             is_perl_builtin_with_no_arguments
28 40     40   180936 precedence_of);
  40         742  
29              
30             our $VERSION = 97;
31              
32              
33 40     40   299 use constant supported_parameters => ();
  40         91  
  40         2709  
34 40     40   215 use constant default_severity => $Perl::Critic::Utils::SEVERITY_MEDIUM;
  40         106  
  40         2211  
35 40     40   224 use constant default_themes => qw(pulp bugs);
  40         81  
  40         2129  
36 40     40   217 use constant applies_to => 'PPI::Token::Operator';
  40         75  
  40         38138  
37              
38             my %op_postfix = ('++' => 1,
39             '--' => 1);
40              
41             my %op_andor = ('&&' => 1,
42             '||' => 1,
43             '//' => 1,
44             'and' => 1,
45             'or' => 1,
46             'xor' => 1);
47              
48             my %post_control = (if => 1,
49             unless => 1,
50             until => 1,
51             for => 1,
52             foreach => 1,
53             while => 1);
54              
55             my %is_bad_precedence = (precedence_of('=~') => 1,
56             precedence_of('>') => 1,
57             precedence_of('==') => 1);
58             my $stop_precedence = max (keys %is_bad_precedence);
59              
60              
61             sub violates {
62 299     299 1 859603 my ($self, $bang_elem, $document) = @_;
63 299 100       930 if ($bang_elem->content ne '!') { return; }
  182         746  
64 117         559 my $constants;
65              
66             # only report when "!" is at the start of an expression, so "-f ! $x" is
67             # not applicable (though bizarre), or with "! ! $x" look only from the
68             # first "!"
69 117 100       297 if (my $prev = $bang_elem->sprevious_sibling) {
70 23 100       613 if ($prev->isa('PPI::Token::Operator')) {
71 21         52 my $op = $prev->content;
72 21 100       102 if (! $op_andor{$op}) { # but do look following "&&" etc
73 15         36 return;
74             }
75             }
76             }
77              
78 102         1781 my $state = 'prefix';
79 102         169 my $seen_precedence = 1;
80              
81 102         156 my $elem = $bang_elem;
82 102         138 for (;;) {
83 251 50       655 $elem or return; # nothing evil up to end of expression
84 251 100       512 $elem = $elem->snext_sibling
85             or return; # nothing evil up to end of expression
86              
87 231 100       4693 if ($elem->isa('PPI::Token::Cast')) {
88             # "\ &foo" is a single form, not a function call
89 6         17 $elem = _next_cast_operand ($elem);
90 6         12 $state = 'postfix';
91 6         12 next;
92             }
93              
94 225 100       650 if ($elem->isa('PPI::Token::Symbol')) {
95 60         103 $state = 'postfix';
96 60 100       138 if ($elem->content =~ /^&/) {
97 7 100       49 if (my $after = $elem->snext_sibling) {
98 6 100       124 if ($after->isa('PPI::Structure::List')) {
99 2         13 $elem = $after; # "! &foo() == 1"
100 2         6 next;
101             }
102             }
103             # "! &foo ..." varargs function call, eats to "," or ";"
104 5         33 return;
105             }
106 53         243 next; # "! $x" etc
107             }
108              
109 165 100       446 if ($elem->isa('PPI::Token::Operator')) {
110 112         236 my $op = $elem->content;
111              
112 112 100 100     672 if ($state eq 'postfix' && $op_postfix{$op}) {
113 1         3 next; # stay in postfix state after '++' or '--'
114             }
115 111 100 100     307 if ($state eq 'prefix' && $op eq '<') {
116             # in prefix position assume "<" is "<STDIN>" glob or readline
117 4         10 $elem = _next_gt ($elem);
118 4         9 $state = 'postfix';
119 4         7 next; # can leave $elem undef for something dodgy like "! < 123"
120             }
121 107   50     265 my $precedence = precedence_of($op) || return;
122              
123 107 100       1432 if ($precedence > $stop_precedence) {
124 18         55 return; # something below "==" etc, expression to ! is ok
125             }
126 89 100 100     331 if (($op eq '==' || $op eq '!=') && _snext_is_bang($elem)) {
      100        
127 2         38 return; # special case "! $x == ! $y" is ok
128             }
129 87 100       173 if ($op eq '->') {
130 10 50       20 if (my $method = $elem->snext_sibling) {
131 10         193 $elem = $method;
132 10         16 $state = 'postfix';
133 10 50       20 if (my $after = $method->snext_sibling) {
134 10 100       197 if ($after->isa('PPI::Token::Operator')) {
135 9         16 next; # "! $foo->bar == 1"
136             }
137 1 50       10 if ($after->isa('PPI::Structure::List')) {
138 1         7 $elem = $after; # "! $foo->bar() == 1"
139 1         3 next;
140             }
141             # bogosity "$foo->bar 123, 456" or the like
142 0         0 return;
143             }
144             }
145             }
146              
147 77 100 100     323 if ($seen_precedence <= $precedence && $is_bad_precedence{$precedence}) {
148             # $op is a compare, so bad
149 49         206 return $self->violation
150             ("Logical \"!\" attempted with a compare \"$op\"",
151             '', $bang_elem);
152             }
153 28         77 $seen_precedence = max ($precedence, $seen_precedence);
154 28         56 $state = 'prefix';
155 28         41 next;
156             }
157              
158 53 100       176 if ($elem->isa('PPI::Token::Word')) {
159 24         61 my $word = $elem->content;
160              
161 24 50       121 if ($post_control{$word}) {
162 0         0 return; # postfix control like "$foo = ! $foo if ..." ends expression
163             }
164 24 100       78 if (is_perl_builtin_with_no_arguments ($word)) {
165             # eg "! time ..."
166             # "time" is a single token, look at operators past it
167 5         91 $state = 'postfix';
168 5         11 next;
169             }
170              
171 19   33     435 $constants ||= _constants ($document);
172 19 100       51 if (exists $constants->{$word}) {
173             # eg. use constant FOO => 456;
174             # ! FOO ...
175             # the FOO is a single token, look at operators past it
176 5         10 $state = 'postfix';
177 5         9 next;
178             }
179              
180 14 100       42 my $next = $elem->snext_sibling
181             or return; # "! FOO" expression ending at a bareword
182              
183 12 100       275 if ($next->isa('PPI::Structure::List')) {
184             # "! FOO(...)" function call
185 3         22 $elem = next;
186 0         0 $state = 'postfix';
187 0         0 next;
188             }
189              
190 9 100       60 if (is_perl_builtin ($word)) {
191 3         65 return; # builtins all taking args, eating "," or ";"
192             }
193              
194 6 100       110 if ($next->isa('PPI::Token::Operator')) {
195 5         15 my $op = $next->content;
196 5 100       27 if ($op eq '<') {
197 4 100       11 if (_next_gt ($next)) {
198             # "! FOO <*.c>" assumed to be glob passed to varargs func, it
199             # ends at "," or ";" so nothing bad for "!"
200 2         8 return;
201             }
202             }
203             # other "! FOO > 123" assumed to be a constant
204 3         6 $state = 'postfix';
205 3         8 next;
206             }
207              
208             # otherwise word is a no parens call, like "foo 123, 456"
209             # exactly how this parses depends on the prototype, but there's
210             # going to be a "," or ";" terminating, so our "!" is ok
211 1         3 return;
212             }
213             }
214              
215 0         0 return;
216             }
217              
218             sub _snext_is_bang {
219 27     27   54 my ($elem) = @_;
220 27         58 my $next = $elem->snext_sibling;
221 27   66     711 return ($next
222             && $next->isa('PPI::Token::Operator')
223             && $next eq '!');
224             }
225              
226             # return the next ">" operator following $elem, or undef if no such
227             sub _next_gt {
228 8     8   18 my ($elem) = @_;
229 8         22 while ($elem = $elem->snext_sibling) {
230 16 100 100     376 if ($elem->isa('PPI::Token::Operator') && $elem eq '>') {
231 6         84 last;
232             }
233             }
234 8         57 return $elem;
235             }
236              
237             # $elem is a PPI::Token::Cast, return its operand elem, meaning the next
238             # non-Cast (usually a Symbol). Return undef if no non-cast, for something
239             # dodgy like "\" with nothing following.
240             sub _next_cast_operand {
241 6     6   13 my ($elem) = @_;
242 6         20 while ($elem = $elem->snext_sibling) {
243 12 100       264 if (! $elem->isa('PPI::Token::Cast')) {
244 6         12 last;
245             }
246             }
247 6         13 return $elem;
248             }
249              
250             # return a hashref which has keys for all the "use constant"s defined in
251             # $document
252             sub _constants {
253 19     19   41 my ($document) = @_;
254 19   33     93 return ($document->{__PACKAGE__.'.NotWithCompareConstants'} ||= do {
255 19         113 require Perl::Critic::Policy::ValuesAndExpressions::ConstantBeforeLt;
256 19         37 my %constants;
257             $document->find
258             (sub {
259 253     253   2185 my ($document, $elem) = @_;
260 253         427 @constants{ Perl::Critic::Policy::ValuesAndExpressions::ConstantBeforeLt::_use_constants($elem) }
261             = (); # hash slice
262 253         417 return 0; # no-match, and continue
263 19         103 });
264 19         270 \%constants;
265             });
266             }
267              
268             1;
269             __END__
270              
271             =for stopwords booleans varargs builtins args Ryde
272              
273             =head1 NAME
274              
275             Perl::Critic::Policy::ValuesAndExpressions::NotWithCompare - logical not used with compare
276              
277             =head1 DESCRIPTION
278              
279             This policy is part of the L<C<Perl::Critic::Pulp>|Perl::Critic::Pulp>
280             add-on. It picks up some cases of logical not C<!> used with a comparison,
281             like
282              
283             ! $x =~ /^[123]/ # bad
284             ! $x + $y >= $z # bad
285              
286             In each case precedence means Perl parses this as C<< (!$x) >>, like
287              
288             (! $x) =~ /^[123]/
289             (! $x) + $y >= $z
290              
291             rather than a negated comparison. Usually this is a mistake, so this policy
292             is under the "bugs" theme (see L<Perl::Critic/POLICY THEMES>).
293              
294             As a special case, C<!> on both sides of C<< == >> or C<< != >> is allowed,
295             since it's quite a good way to compare booleans.
296              
297             !$x == !$y # ok
298             !$x != !$y # ok
299              
300             =head1 LIMITATIONS
301              
302             User functions called without parentheses are assumed to be usual varargs
303             style. But a prototype may mean that's not the case, letting a bad
304             C<!>-with-compare expression to go undetected.
305              
306             ! userfunc $x == 123 # indeterminate
307             # without prototype would be ok: ! (userfunc ($x==123))
308             # with ($) prototype would be bad: (! userfunc($x)) == 123
309              
310             Perl builtins with no args, and constant subs created with C<use constant>
311             or C<sub FOO () {...}> in the file under test are recognised. Hopefully
312             anything else too weird is rare.
313              
314             ! time == 1 # bad
315              
316             use constant FIVE => 5;
317             ! FIVE < 1 # bad
318              
319             sub name () { "foo" }
320             ! name =~ /bar/ # bad
321              
322             =head1 SEE ALSO
323              
324             L<Perl::Critic::Pulp>,
325             L<Perl::Critic>
326              
327             =head1 HOME PAGE
328              
329             http://user42.tuxfamily.org/perl-critic-pulp/index.html
330              
331             =head1 COPYRIGHT
332              
333             Copyright 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017 Kevin Ryde
334              
335             Perl-Critic-Pulp is free software; you can redistribute it and/or modify it
336             under the terms of the GNU General Public License as published by the Free
337             Software Foundation; either version 3, or (at your option) any later
338             version.
339              
340             Perl-Critic-Pulp is distributed in the hope that it will be useful, but
341             WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
342             or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
343             more details.
344              
345             You should have received a copy of the GNU General Public License along with
346             Perl-Critic-Pulp. If not, see <http://www.gnu.org/licenses/>.
347              
348             =cut