File Coverage

blib/lib/Perl/Critic/Policy/ValuesAndExpressions/ProhibitMismatchedOperators.pm
Criterion Covered Total %
statement 45 63 71.4
branch 15 42 35.7
condition 5 38 13.1
subroutine 14 16 87.5
pod 4 5 80.0
total 83 164 50.6


line stmt bran cond sub pod time code
1             package Perl::Critic::Policy::ValuesAndExpressions::ProhibitMismatchedOperators;
2 40     40   25577 use 5.010001;
  40         167  
3 40     40   169 use strict;
  40         68  
  40         766  
4 40     40   186 use warnings;
  40         60  
  40         1527  
5 40     40   150 use Readonly;
  40         65  
  40         2221  
6              
7 40     40   213 use Perl::Critic::Utils qw{ :booleans :severities hashify };
  40         61  
  40         2075  
8 40     40   6078 use parent 'Perl::Critic::Policy';
  40         70  
  40         238  
9              
10             our $VERSION = '1.156';
11              
12             #-----------------------------------------------------------------------------
13              
14             Readonly::Scalar my $DESC => q<Mismatched operator>;
15             Readonly::Scalar my $EXPL => q<Numeric/string operators and operands should match>;
16              
17             # token compatibility [ numeric, string ]
18             Readonly::Hash my %TOKEN_COMPATIBILITY => (
19             'PPI::Token::Number' => [$TRUE, $FALSE],
20             'PPI::Token::Symbol' => [$TRUE, $TRUE ],
21             'PPI::Token::Quote' => [$FALSE, $TRUE ],
22             );
23              
24             Readonly::Hash my %FILE_OPERATOR_COMPATIBILITY =>
25             map {; "-$_" => [$TRUE, $FALSE] }
26             qw< r w x o R W X O e z s f d l p S b c t u g k T B M A >;
27              
28             Readonly::Scalar my $TOKEN_COMPATIBILITY_INDEX_NUMERIC => 0;
29             Readonly::Scalar my $TOKEN_COMPATIBILITY_INDEX_STRING => 1;
30              
31             Readonly::Hash my %OPERATOR_TYPES => (
32             # numeric
33             (
34             map { $_ => $TOKEN_COMPATIBILITY_INDEX_NUMERIC }
35             qw[ == != > >= < <= + - * / += -= *= /= ]
36             ),
37             # string
38             map { $_ => $TOKEN_COMPATIBILITY_INDEX_STRING }
39             qw< eq ne lt gt le ge . .= >,
40             );
41              
42             Readonly::Scalar my $TOKEN_COMPATIBILITY_SPECIAL_STRING_OPERATOR => qw{+};
43             Readonly::Hash my %SPECIAL_STRING_VALUES =>
44             hashify( qw('nan' 'inf' '-inf' '+inf') );
45              
46             #-----------------------------------------------------------------------------
47              
48 90     90 0 651 sub supported_parameters { return () }
49 75     75 1 243 sub default_severity { return $SEVERITY_MEDIUM }
50 74     74 1 293 sub default_themes { return qw< core bugs certrule > }
51 31     31 1 66 sub applies_to { return 'PPI::Token::Operator' }
52              
53             #-----------------------------------------------------------------------------
54              
55             sub violates {
56 135     135 1 196 my ($self, $elem) = @_;
57              
58 135         237 my $elem_text = $elem->content();
59              
60 135 100       614 return if not exists $OPERATOR_TYPES{$elem_text};
61              
62 17 50       93 my $leading_operator = _get_potential_leading_operator($elem)
63             or return;
64              
65 17 50       59 my $next_elem = $elem->snext_sibling() or return;
66              
67 17 50       303 if ( $next_elem->isa('PPI::Token::Operator') ) {
68 0         0 $elem_text .= $next_elem->content();
69 0         0 $next_elem = $next_elem->snext_sibling();
70             }
71              
72 17 50       31 return if not exists $OPERATOR_TYPES{$elem_text};
73 17         91 my $operator_type = $OPERATOR_TYPES{$elem_text};
74              
75 17         73 my $leading_operator_compatibility = _get_token_compatibility($leading_operator);
76 17         83 my $next_compatibility = _get_token_compatibility($next_elem);
77              
78             return if
79             (
80 17 50 33     103 ! defined $leading_operator_compatibility
      33        
      33        
81             || $leading_operator_compatibility->[$operator_type]
82             )
83             && (
84             ! defined $next_compatibility
85             || $next_compatibility->[$operator_type]
86             );
87              
88             return if
89 0 0 0     0 $operator_type
      0        
      0        
90             && defined $leading_operator_compatibility
91             && ! $leading_operator_compatibility->[$operator_type]
92             && _have_stringy_x($leading_operator); # RT 54524
93              
94 0 0       0 return if $self->_is_special_string_number_addion($elem_text, $leading_operator, $next_elem);
95              
96 0         0 return $self->violation($DESC, $EXPL, $elem);
97             }
98              
99             #-----------------------------------------------------------------------------
100              
101             sub _get_token_compatibility {
102 51     51   62 my ($elem) = @_;
103              
104 51 50       73 return $FILE_OPERATOR_COMPATIBILITY{ $elem->content() }
105             if _is_file_operator($elem);
106              
107 51         90 for my $class (keys %TOKEN_COMPATIBILITY) {
108 117 100       717 return $TOKEN_COMPATIBILITY{$class} if $elem->isa($class);
109             }
110              
111 0         0 return;
112             }
113              
114             #-----------------------------------------------------------------------------
115              
116             sub _have_stringy_x {
117 0     0   0 my ($elem) = @_;
118              
119 0 0       0 return if not $elem;
120              
121 0 0       0 my $prev_oper = $elem->sprevious_sibling() or return;
122              
123 0 0       0 return if not $prev_oper->isa('PPI::Token::Operator');
124 0 0       0 return if 'x' ne $prev_oper->content();
125              
126 0         0 return !! $prev_oper->sprevious_sibling();
127             }
128              
129             #-----------------------------------------------------------------------------
130              
131             sub _get_potential_leading_operator {
132 17     17   25 my ($elem) = @_;
133              
134 17 50       33 my $previous_element = $elem->sprevious_sibling() or return;
135              
136 17 50       358 if ( _get_token_compatibility($previous_element) ) {
137 17         103 my $previous_sibling = $previous_element->sprevious_sibling();
138 17 50 66     280 if (
139             $previous_sibling and _is_file_operator($previous_sibling)
140             ) {
141 0         0 $previous_element = $previous_sibling;
142             }
143             }
144              
145 17         44 return $previous_element;
146             }
147              
148             #-----------------------------------------------------------------------------
149              
150             sub _is_file_operator {
151 66     66   79 my ($elem) = @_;
152              
153 66 50       117 return if not $elem;
154 66 50       185 return if not $elem->isa('PPI::Token::Operator');
155 0           return !! $FILE_OPERATOR_COMPATIBILITY{ $elem->content() };
156             }
157              
158             #-----------------------------------------------------------------------------
159              
160             sub _is_special_string_number_addion {
161 0     0     my ($self, $elem_operator, $element_1, $element_2, $check_recursive) = @_;
162              
163             return 1 if $elem_operator
164             && $elem_operator eq $TOKEN_COMPATIBILITY_SPECIAL_STRING_OPERATOR
165 0 0 0       && $SPECIAL_STRING_VALUES{lc($element_1->content()//0)}
      0        
      0        
      0        
      0        
166             && $element_2->isa('PPI::Token::Number')
167             && $element_2->content() == 0;
168 0 0 0       return 1 if !$check_recursive && $self->_is_special_string_number_addion($elem_operator, $element_2, $element_1, 1);
169              
170 0           return;
171             }
172              
173             1;
174              
175             __END__
176              
177             #-----------------------------------------------------------------------------
178              
179             =pod
180              
181             =head1 NAME
182              
183             Perl::Critic::Policy::ValuesAndExpressions::ProhibitMismatchedOperators - Don't mix numeric operators with string operands, or vice-versa.
184              
185              
186             =head1 AFFILIATION
187              
188             This Policy is part of the core L<Perl::Critic|Perl::Critic>
189             distribution.
190              
191              
192             =head1 DESCRIPTION
193              
194             Using the wrong operator type for a value can obscure coding intent
195             and possibly lead to subtle errors. An example of this is mixing a
196             string equality operator with a numeric value, or vice-versa.
197              
198             if ($foo == 'bar') {} #not ok
199             if ($foo eq 'bar') {} #ok
200             if ($foo eq 123) {} #not ok
201             if ($foo == 123) {} #ok
202              
203              
204             =head1 CONFIGURATION
205              
206             This Policy is not configurable except for the standard options.
207              
208             =for stopwords NaN struct
209              
210             =head1 NOTES
211              
212             If L<warnings|warnings> are enabled, the Perl interpreter usually
213             warns you about using mismatched operators at run-time. This Policy
214             does essentially the same thing, but at author-time. That way, you
215             can find out about them sooner.
216              
217             Perl handles the strings 'NaN' and 'inf' as special numbers and creates an NV struct when compared with a numeric operator.
218             Although not necessary it is allowed to write code such as:
219             my $i = 'inf'+0;
220             This pattern helps others understand that the variable is indeed the Infinite or NaN numbers as Perl interprets them.
221             Only these two special string numbers are allowed to have the '+' operator which would otherwise be allowed only for strings.
222              
223              
224             =head1 AUTHOR
225              
226             Peter Guzis <pguzis@cpan.org>
227              
228              
229             =head1 COPYRIGHT
230              
231             Copyright (c) 2006-2023 Peter Guzis. All rights reserved.
232              
233             This program is free software; you can redistribute it and/or modify
234             it under the same terms as Perl itself. The full text of this license
235             can be found in the LICENSE file included with this module.
236              
237             =cut
238              
239             # Local Variables:
240             # mode: cperl
241             # cperl-indent-level: 4
242             # fill-column: 78
243             # indent-tabs-mode: nil
244             # c-indentation-style: bsd
245             # End:
246             # ex: set ts=8 sts=4 sw=4 tw=78 ft=perl expandtab shiftround :