File Coverage

blib/lib/Perl/Critic/Policy/Variables/RequireNegativeIndices.pm
Criterion Covered Total %
statement 101 103 98.0
branch 59 80 73.7
condition 11 18 61.1
subroutine 19 19 100.0
pod 4 5 80.0
total 194 225 86.2


line stmt bran cond sub pod time code
1             package Perl::Critic::Policy::Variables::RequireNegativeIndices;
2              
3 40     40   29669 use 5.010001;
  40         185  
4 40     40   307 use strict;
  40         124  
  40         928  
5 40     40   260 use warnings;
  40         128  
  40         1162  
6 40     40   264 use Readonly;
  40         158  
  40         2107  
7              
8 40     40   294 use Perl::Critic::Utils qw{ :severities };
  40         116  
  40         2126  
9 40     40   5104 use parent 'Perl::Critic::Policy';
  40         147  
  40         285  
10              
11             our $VERSION = '1.146';
12              
13             #-----------------------------------------------------------------------------
14              
15             Readonly::Scalar my $DESC => q{Negative array index should be used};
16             Readonly::Scalar my $EXPL => [ 88 ];
17              
18             #-----------------------------------------------------------------------------
19              
20 93     93 0 1622 sub supported_parameters { return () }
21 87     87 1 428 sub default_severity { return $SEVERITY_HIGH }
22 86     86 1 360 sub default_themes { return qw( core maintenance pbp ) }
23 36     36 1 156 sub applies_to { return 'PPI::Structure::Subscript' }
24              
25             #-----------------------------------------------------------------------------
26              
27             sub violates {
28 44     44 1 101 my ( $self, $elem, $doc ) = @_;
29              
30 44 100       129 return if $elem->braces ne '[]';
31 39         433 my ($name, $isref) = _is_bad_index( $elem );
32 39 100       124 return if ( !$name );
33 25 100       62 return if !_is_array_name( $elem, $name, $isref );
34 13         59 return $self->violation( $DESC, $EXPL, $elem );
35             }
36              
37             Readonly::Scalar my $MAX_EXPRESSION_COMPLEXETY => 4;
38              
39             sub _is_bad_index {
40             # return (varname, 0|1) if this could be a violation
41 39     39   74 my ( $elem ) = @_;
42              
43 39         122 my @children = $elem->schildren();
44 39 50       369 return if @children != 1; # too complex
45 39 50       143 return if !$children[0]->isa( 'PPI::Statement::Expression'); # too complex
46              
47             # This is the expression elements that compose the array indexing
48 39         101 my @expr = $children[0]->schildren();
49 39 50 33     506 return if !@expr || @expr > $MAX_EXPRESSION_COMPLEXETY;
50 39         92 my ($name, $isref, $isindex) = _is_bad_var_in_index(\@expr);
51 39 100       109 return if !$name;
52 25 100 66     97 return $name, $isref if !@expr && $isindex;
53 24 50       63 return if !_is_minus_number(@expr);
54 24         80 return $name, $isref;
55             }
56              
57             sub _is_bad_var_in_index {
58             # return (varname, isref=0|1, isindex=0|1) if this could be a violation
59 39     39   116 my ( $expr ) = @_;
60              
61 39 100       292 if ( $expr->[0]->isa('PPI::Token::ArrayIndex') ) {
    100          
    100          
62             # [$#arr]
63 6         16 return _arrayindex($expr);
64             }
65             elsif ( $expr->[0]->isa('PPI::Token::Cast') ) {
66             # [$#{$arr} ...] or [$#$arr ...] or [@{$arr} ...] or [@$arr ...]
67 14         38 return _cast($expr);
68             }
69             elsif ($expr->[0]->isa('PPI::Token::Symbol')) {
70             # [@arr ...]
71 13         36 return _symbol($expr);
72             }
73              
74 6         13 return;
75             }
76              
77             sub _arrayindex {
78             # return (varname, isref=0|1, isindex=0|1) if this could be a violation
79 6     6   13 my ( $expr ) = @_;
80 6         16 my $arrindex = shift @{$expr};
  6         13  
81 6 50       21 if ($arrindex->content =~ m/\A \$[#] (.*) \z /xms) { # What else could it be???
82 6         71 return $1, 0, 1;
83             }
84 0         0 return;
85             }
86              
87             sub _cast {
88             # return (varname, isref=0|1, isindex=0|1) if this could be a violation
89 14     14   30 my ( $expr ) = @_;
90 14         26 my $cast = shift @{$expr};
  14         32  
91 14 50 66     50 if ( $cast eq q{$#} || $cast eq q{@} ) { ## no critic(RequireInterpolationOfMetachars)
92 14 100       315 my $isindex = $cast eq q{$#} ? 1 : 0; ## no critic(RequireInterpolationOfMetachars)
93 14         165 my $arrvar = shift @{$expr};
  14         28  
94 14 100       71 if ($arrvar->isa('PPI::Structure::Block')) {
    50          
95             # look for [$#{$arr} ...] or [@{$arr} ...]
96 8         26 my @blockchildren = $arrvar->schildren();
97 8 50       75 return if @blockchildren != 1;
98 8 50       37 return if !$blockchildren[0]->isa('PPI::Statement');
99 8         24 my @ggg = $blockchildren[0]->schildren;
100 8 100       83 return if @ggg != 1;
101 6 50       25 return if !$ggg[0]->isa('PPI::Token::Symbol');
102 6 50       17 if ($ggg[0] =~ m/\A \$ (.*) \z/xms) {
103 6         77 return $1, 1, $isindex;
104             }
105             }
106             elsif ( $arrvar->isa('PPI::Token::Symbol') ) {
107             # look for [$#$arr ...] or [@$arr ...]
108 6 50       19 if ($arrvar =~ m/\A \$ (.*) \z/xms) {
109 6         69 return $1, 1, $isindex;
110             }
111             }
112             }
113 0         0 return;
114             }
115              
116             sub _symbol {
117             # return (varname, isref=0|1, isindex=0|1) if this could be a violation
118 13     13   29 my ( $expr ) = @_;
119 13         23 my $arrvar = shift @{$expr};
  13         28  
120 13 100       44 if ($arrvar =~ m/\A \@ (.*) \z/xms) {
121 7         75 return $1, 0, 0;
122             }
123 6         43 return;
124             }
125              
126             sub _is_minus_number { # return true if @expr looks like "- n"
127 24     24   50 my @expr = @_;
128              
129 24 50       58 return if !@expr;
130              
131 24 50       66 return if @expr != 2;
132              
133 24         45 my $op = shift @expr;
134 24 50       96 return if !$op->isa('PPI::Token::Operator');
135 24 50       74 return if $op ne q{-};
136              
137 24         400 my $number = shift @expr;
138 24 50       80 return if !$number->isa('PPI::Token::Number');
139              
140 24         65 return 1;
141             }
142              
143             sub _is_array_name { # return true if name and isref matches
144 25     25   58 my ( $elem, $name, $isref ) = @_;
145              
146 25         80 my $sib = $elem->sprevious_sibling;
147 25 50       688 return if !$sib;
148              
149 25 100 66     119 if ($sib->isa('PPI::Token::Operator') && $sib eq '->') {
150 8 100       135 return if ( !$isref );
151 5         11 $isref = 0;
152 5         16 $sib = $sib->sprevious_sibling;
153 5 50       112 return if !$sib;
154             }
155              
156 22 100       74 return if !$sib->isa('PPI::Token::Symbol');
157 21 100       200 return if $sib !~ m/\A \$ \Q$name\E \z/xms;
158              
159 17         145 my $cousin = $sib->sprevious_sibling;
160 17 100       329 return if $isref ^ _is_dereferencer( $cousin );
161 13 50 66     50 return if $isref && _is_dereferencer( $cousin->sprevious_sibling );
162              
163 13         49 return $elem;
164             }
165              
166             sub _is_dereferencer { # must return 0 or 1, not undef
167 21     21   98 my $elem = shift;
168              
169 21 100       94 return 0 if !$elem;
170 6 50 66     33 return 1 if $elem->isa('PPI::Token::Operator') && $elem eq '->';
171 6 100       50 return 1 if $elem->isa('PPI::Token::Cast');
172 1         8 return 0;
173             }
174              
175             1;
176              
177             #-----------------------------------------------------------------------------
178              
179             __END__
180              
181             =pod
182              
183             =for stopwords performant
184              
185             =head1 NAME
186              
187             Perl::Critic::Policy::Variables::RequireNegativeIndices - Negative array index should be used.
188              
189              
190             =head1 AFFILIATION
191              
192             This Policy is part of the core L<Perl::Critic|Perl::Critic>
193             distribution.
194              
195              
196             =head1 DESCRIPTION
197              
198             Perl treats a negative array subscript as an offset from the end. Given
199             this, the preferred way to get the last element is C<$x[-1]>, not
200             C<$x[$#x]> or C<$x[@x-1]>, and the preferred way to get the next-to-last
201             is C<$x[-2]>, not C<$x[$#x-1> or C<$x[@x-2]>.
202              
203             The biggest argument against the non-preferred forms is that B<their
204             semantics change> when the computed index becomes negative. If C<@x>
205             contains at least two elements, C<$x[$#x-1]> and C<$x[@x-2]> are
206             equivalent to C<$x[-2]>. But if it contains a single element,
207             C<$x[$#x-1]> and C<$x[@x-2]> are both equivalent to C<$x[-1]>. Simply
208             put, the preferred form is more likely to do what you actually want.
209              
210             As Conway points out, the preferred forms also perform better, are more
211             readable, and are easier to maintain.
212              
213             This policy notices all of the simple forms of the above problem, but
214             does not recognize any of these more complex examples:
215              
216             $some->[$data_structure]->[$#{$some->[$data_structure]} -1];
217             my $ref = \@arr; $ref->[$#arr];
218              
219              
220             =head1 CONFIGURATION
221              
222             This Policy is not configurable except for the standard options.
223              
224              
225             =head1 AUTHOR
226              
227             Chris Dolan <cdolan@cpan.org>
228              
229              
230             =head1 COPYRIGHT
231              
232             Copyright (c) 2006-2011 Chris Dolan.
233              
234             This program is free software; you can redistribute it and/or modify
235             it under the same terms as Perl itself.
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 :