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   28322 use 5.010001;
  40         236  
4 40     40   295 use strict;
  40         125  
  40         919  
5 40     40   273 use warnings;
  40         152  
  40         1165  
6 40     40   273 use Readonly;
  40         139  
  40         2017  
7              
8 40     40   294 use Perl::Critic::Utils qw{ :severities };
  40         175  
  40         2033  
9 40     40   4926 use parent 'Perl::Critic::Policy';
  40         169  
  40         244  
10              
11             our $VERSION = '1.148';
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 1798 sub supported_parameters { return () }
21 87     87 1 415 sub default_severity { return $SEVERITY_HIGH }
22 86     86 1 454 sub default_themes { return qw( core maintenance pbp ) }
23 36     36 1 124 sub applies_to { return 'PPI::Structure::Subscript' }
24              
25             #-----------------------------------------------------------------------------
26              
27             sub violates {
28 44     44 1 97 my ( $self, $elem, $doc ) = @_;
29              
30 44 100       113 return if $elem->braces ne '[]';
31 39         440 my ($name, $isref) = _is_bad_index( $elem );
32 39 100       119 return if ( !$name );
33 25 100       61 return if !_is_array_name( $elem, $name, $isref );
34 13         60 return $self->violation( $DESC, $EXPL, $elem );
35             }
36              
37             Readonly::Scalar my $MAX_EXPRESSION_COMPLEXITY => 4;
38              
39             sub _is_bad_index {
40             # return (varname, 0|1) if this could be a violation
41 39     39   76 my ( $elem ) = @_;
42              
43 39         116 my @children = $elem->schildren();
44 39 50       377 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         93 my @expr = $children[0]->schildren();
49 39 50 33     492 return if !@expr || @expr > $MAX_EXPRESSION_COMPLEXITY;
50 39         101 my ($name, $isref, $isindex) = _is_bad_var_in_index(\@expr);
51 39 100       113 return if !$name;
52 25 100 66     90 return $name, $isref if !@expr && $isindex;
53 24 50       62 return if !_is_minus_number(@expr);
54 24         78 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   106 my ( $expr ) = @_;
60              
61 39 100       257 if ( $expr->[0]->isa('PPI::Token::ArrayIndex') ) {
    100          
    100          
62             # [$#arr]
63 6         17 return _arrayindex($expr);
64             }
65             elsif ( $expr->[0]->isa('PPI::Token::Cast') ) {
66             # [$#{$arr} ...] or [$#$arr ...] or [@{$arr} ...] or [@$arr ...]
67 14         37 return _cast($expr);
68             }
69             elsif ($expr->[0]->isa('PPI::Token::Symbol')) {
70             # [@arr ...]
71 13         41 return _symbol($expr);
72             }
73              
74 6         15 return;
75             }
76              
77             sub _arrayindex {
78             # return (varname, isref=0|1, isindex=0|1) if this could be a violation
79 6     6   16 my ( $expr ) = @_;
80 6         10 my $arrindex = shift @{$expr};
  6         14  
81 6 50       21 if ($arrindex->content =~ m/\A \$[#] (.*) \z /xms) { # What else could it be???
82 6         69 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   29 my ( $expr ) = @_;
90 14         25 my $cast = shift @{$expr};
  14         28  
91 14 50 66     46 if ( $cast eq q{$#} || $cast eq q{@} ) { ## no critic(RequireInterpolationOfMetachars)
92 14 100       296 my $isindex = $cast eq q{$#} ? 1 : 0; ## no critic(RequireInterpolationOfMetachars)
93 14         164 my $arrvar = shift @{$expr};
  14         28  
94 14 100       69 if ($arrvar->isa('PPI::Structure::Block')) {
    50          
95             # look for [$#{$arr} ...] or [@{$arr} ...]
96 8         25 my @blockchildren = $arrvar->schildren();
97 8 50       74 return if @blockchildren != 1;
98 8 50       36 return if !$blockchildren[0]->isa('PPI::Statement');
99 8         25 my @ggg = $blockchildren[0]->schildren;
100 8 100       80 return if @ggg != 1;
101 6 50       26 return if !$ggg[0]->isa('PPI::Token::Symbol');
102 6 50       19 if ($ggg[0] =~ m/\A \$ (.*) \z/xms) {
103 6         75 return $1, 1, $isindex;
104             }
105             }
106             elsif ( $arrvar->isa('PPI::Token::Symbol') ) {
107             # look for [$#$arr ...] or [@$arr ...]
108 6 50       17 if ($arrvar =~ m/\A \$ (.*) \z/xms) {
109 6         66 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   25 my ( $expr ) = @_;
119 13         25 my $arrvar = shift @{$expr};
  13         26  
120 13 100       37 if ($arrvar =~ m/\A \@ (.*) \z/xms) {
121 7         76 return $1, 0, 0;
122             }
123 6         40 return;
124             }
125              
126             sub _is_minus_number { # return true if @expr looks like "- n"
127 24     24   57 my @expr = @_;
128              
129 24 50       58 return if !@expr;
130              
131 24 50       70 return if @expr != 2;
132              
133 24         46 my $op = shift @expr;
134 24 50       84 return if !$op->isa('PPI::Token::Operator');
135 24 50       76 return if $op ne q{-};
136              
137 24         404 my $number = shift @expr;
138 24 50       84 return if !$number->isa('PPI::Token::Number');
139              
140 24         73 return 1;
141             }
142              
143             sub _is_array_name { # return true if name and isref matches
144 25     25   54 my ( $elem, $name, $isref ) = @_;
145              
146 25         77 my $sib = $elem->sprevious_sibling;
147 25 50       654 return if !$sib;
148              
149 25 100 66     123 if ($sib->isa('PPI::Token::Operator') && $sib eq '->') {
150 8 100       130 return if ( !$isref );
151 5         12 $isref = 0;
152 5         15 $sib = $sib->sprevious_sibling;
153 5 50       115 return if !$sib;
154             }
155              
156 22 100       92 return if !$sib->isa('PPI::Token::Symbol');
157 21 100       180 return if $sib !~ m/\A \$ \Q$name\E \z/xms;
158              
159 17         136 my $cousin = $sib->sprevious_sibling;
160 17 100       327 return if $isref ^ _is_dereferencer( $cousin );
161 13 50 66     51 return if $isref && _is_dereferencer( $cousin->sprevious_sibling );
162              
163 13         50 return $elem;
164             }
165              
166             sub _is_dereferencer { # must return 0 or 1, not undef
167 21     21   100 my $elem = shift;
168              
169 21 100       98 return 0 if !$elem;
170 6 50 66     34 return 1 if $elem->isa('PPI::Token::Operator') && $elem eq '->';
171 6 100       48 return 1 if $elem->isa('PPI::Token::Cast');
172 1         6 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 :