1 ##############################################################################
2 # $URL: http://perlcritic.tigris.org/svn/perlcritic/trunk/Perl-Critic/lib/Perl/Critic/Policy/Variables/RequireNegativeIndices.pm $
3 # $Date: 2008-07-03 10:19:10 -0500 (Thu, 03 Jul 2008) $
6 ##############################################################################
8 package Perl::Critic::Policy::Variables::RequireNegativeIndices;
15 use Perl::Critic::Utils qw{ :severities };
16 use base 'Perl::Critic::Policy';
18 our $VERSION = '1.088';
20 #-----------------------------------------------------------------------------
22 Readonly::Scalar my $DESC => q{Negative array index should be used};
23 Readonly::Scalar my $EXPL => [ 88 ];
25 #-----------------------------------------------------------------------------
27 sub supported_parameters { return () }
28 sub default_severity { return $SEVERITY_HIGH }
29 sub default_themes { return qw( core maintenance pbp ) }
30 sub applies_to { return 'PPI::Structure::Subscript' }
32 #-----------------------------------------------------------------------------
35 my ( $self, $elem, $doc ) = @_;
37 return if $elem->braces ne '[]';
38 my ($name, $isref) = _is_bad_index( $elem );
40 return if !_is_array_name( $elem, $name, $isref );
41 return $self->violation( $DESC, $EXPL, $elem );
44 Readonly::Scalar my $MAX_EXPRESSION_COMPLEXETY => 4;
47 # return (varname, 0|1) if this could be a violation
50 my @children = $elem->schildren();
51 return if @children != 1; # too complex
52 return if !$children[0]->isa( 'PPI::Statement::Expression'); # too complex
54 # This is the expression elements that compose the array indexing
55 my @expr = $children[0]->schildren();
56 return if !@expr || @expr > $MAX_EXPRESSION_COMPLEXETY;
57 my ($name, $isref, $isindex) = _is_bad_var_in_index(\@expr);
59 return $name, $isref if !@expr && $isindex;
60 return if !_is_minus_number(@expr);
64 sub _is_bad_var_in_index {
65 # return (varname, isref=0|1, isindex=0|1) if this could be a violation
68 if ( $expr->[0]->isa('PPI::Token::ArrayIndex') ) {
70 return _arrayindex($expr);
72 elsif ( $expr->[0]->isa('PPI::Token::Cast') ) {
73 # [$#{$arr} ...] or [$#$arr ...] or [@{$arr} ...] or [@$arr ...]
76 elsif ($expr->[0]->isa('PPI::Token::Symbol')) {
78 return _symbol($expr);
85 # return (varname, isref=0|1, isindex=0|1) if this could be a violation
87 my $arrindex = shift @{$expr};
88 if ($arrindex->content =~ m/\A \$[#] (.*) \z /xms) { # What else could it be???
95 # return (varname, isref=0|1, isindex=0|1) if this could be a violation
97 my $cast = shift @{$expr};
98 if ( $cast eq q{$#} || $cast eq q{@} ) { ## no critic(RequireInterpolationOfMetachars)
99 my $isindex = $cast eq q{$#} ? 1 : 0; ## no critic(RequireInterpolationOfMetachars)
100 my $arrvar = shift @{$expr};
101 if ($arrvar->isa('PPI::Structure::Block')) {
102 # look for [$#{$arr} ...] or [@{$arr} ...]
103 my @blockchildren = $arrvar->schildren();
104 return if @blockchildren != 1;
105 return if !$blockchildren[0]->isa('PPI::Statement');
106 my @ggg = $blockchildren[0]->schildren;
108 return if !$ggg[0]->isa('PPI::Token::Symbol');
109 if ($ggg[0] =~ m/\A \$ (.*) \z/xms) {
110 return $1, 1, $isindex;
113 elsif ( $arrvar->isa('PPI::Token::Symbol') ) {
114 # look for [$#$arr ...] or [@$arr ...]
115 if ($arrvar =~ m/\A \$ (.*) \z/xms) {
116 return $1, 1, $isindex;
124 # return (varname, isref=0|1, isindex=0|1) if this could be a violation
126 my $arrvar = shift @{$expr};
127 if ($arrvar =~ m/\A \@ (.*) \z/xms) {
133 sub _is_minus_number { # return true if @expr looks like "- n"
138 return if @expr != 2;
140 my $op = shift @expr;
141 return if !$op->isa('PPI::Token::Operator');
142 return if $op ne q{-};
144 my $number = shift @expr;
145 return if !$number->isa('PPI::Token::Number');
150 sub _is_array_name { # return true if name and isref matches
151 my ( $elem, $name, $isref ) = @_;
153 my $sib = $elem->sprevious_sibling;
156 if ($sib->isa('PPI::Token::Operator') && $sib eq '->') {
157 return if ( !$isref );
159 $sib = $sib->sprevious_sibling;
163 return if !$sib->isa('PPI::Token::Symbol');
164 return if $sib !~ m/\A \$ \Q$name\E \z/xms;
166 my $cousin = $sib->sprevious_sibling;
167 return if $isref ^ _is_dereferencer( $cousin );
168 return if $isref && _is_dereferencer( $cousin->sprevious_sibling );
173 sub _is_dereferencer { # must return 0 or 1, not undef
177 return 1 if $elem->isa('PPI::Token::Operator') && $elem eq '->';
178 return 1 if $elem->isa('PPI::Token::Cast');
184 #-----------------------------------------------------------------------------
190 =for stopwords performant
194 Perl::Critic::Policy::Variables::RequireNegativeIndices - Negative array index should be used.
198 This Policy is part of the core L<Perl::Critic> distribution.
203 Conway points out that
217 and the latter are more readable, performant and maintainable. The
218 latter is because the programmer no longer needs to keep two variable
221 This policy notices all of the simple forms of the above problem, but
222 does not recognize any of these more complex examples:
224 $some->[$data_structure]->[$#{$some->[$data_structure]} -1];
231 This Policy is not configurable except for the standard options.
236 Chris Dolan <cdolan@cpan.org>
240 Copyright (C) 2006 Chris Dolan. All rights reserved.
242 This program is free software; you can redistribute it and/or modify
243 it under the same terms as Perl itself.
249 # cperl-indent-level: 4
251 # indent-tabs-mode: nil
252 # c-indentation-style: bsd
254 # ex: set ts=8 sts=4 sw=4 tw=78 ft=perl expandtab shiftround :