File Coverage

blib/lib/Perl/Critic/Policy/References/RequireSigils.pm
Criterion Covered Total %
statement 62 67 92.5
branch 21 26 80.7
condition 3 5 60.0
subroutine 14 15 93.3
pod 4 7 57.1
total 104 120 86.6


line stmt bran cond sub pod time code
1             package Perl::Critic::Policy::References::RequireSigils;
2              
3 5     5   4023 use 5.010001;
  5         22  
4 5     5   26 use strict;
  5         9  
  5         104  
5 5     5   19 use warnings;
  5         9  
  5         194  
6 5     5   22 use Readonly;
  5         7  
  5         286  
7              
8 5     5   27 use Perl::Critic::Utils qw/:severities :classification/;
  5         21  
  5         272  
9 5     5   2285 use base 'Perl::Critic::Policy';
  5         11  
  5         426  
10              
11 5     5   2840 use PPI::Document;
  5         640561  
  5         236  
12 5     5   3465 use PPIx::QuoteLike;
  5         753334  
  5         3278  
13              
14             our $VERSION = '0.0.7';
15              
16             Readonly::Scalar my $DESC => q{Only use arrows for methods};
17             Readonly::Scalar my $EXPL => undef;
18              
19             #-----------------------------------------------------------------------------
20              
21             sub supported_parameters {
22             return (
23             {
24 3     3 0 1862400 name => 'directcast',
25             description => 'Prohibit block-style casting of direct references (not yet available).',
26             default_string => '1',
27             behavior => 'boolean',
28             },
29             {
30             name => 'interpolation',
31             description => 'Check interpolated strings for arrow-like patterns.',
32             default_string => '1',
33             behavior => 'boolean',
34             },
35             );
36             }
37              
38 75     75 1 282308 sub applies_to { return qw/PPI::Token::Operator PPI::Token::Quote::Double/ }
39 14     14 1 136 sub default_severity { return $SEVERITY_LOW }
40 0     0 1 0 sub default_themes { return qw/cosmetic/ }
41              
42             #-----------------------------------------------------------------------------
43              
44             sub invalid {
45 14     14 0 28 my ($self,$elem,$note)=@_;
46 14   50     68 $note//='';
47 14 50       28 if($note) { $note=" ($note)" }
  0         0  
48 14         57 return $self->violation(sprintf("%s%s",$DESC,$note),$EXPL,$elem);
49             }
50              
51             sub operatorViolates {
52 49     49 0 98 my ($self,$elem)=@_;
53 49 100       165 if($elem->content() ne '->') { return }
  31         194  
54 18         107 my $next=$elem->snext_sibling();
55 18 50       518 if(!$next) { return }
  0         0  
56 18 100 66     77 if($next->isa('PPI::Token::Word') && is_method_call($next)) { return }
  4         208  
57 14 100       59 if($next->isa('PPI::Structure::Subscript')) { return $self->invalid($elem) }
  6         18  
58 8 100       36 if($next->isa('PPI::Structure::List')) { return $self->invalid($elem) }
  3         21  
59 5 50       13 if($next->isa('PPI::Token::Cast')) { return $self->invalid($elem) }
  5         16  
60 0         0 return;
61             }
62              
63             sub violates {
64 89     89 1 2535 my ($self,$elem,undef)=@_;
65              
66 89 100       411 if($elem->isa('PPI::Token::Operator')) { return $self->operatorViolates($elem) }
  45         107  
67              
68 44 50       146 if($elem->isa('PPI::Token::Quote')) {
69 44 100       120 if(!$$self{_interpolation}) { return }
  23         49  
70 21         69 my $content=$elem->content();
71 21         163 my $string=PPIx::QuoteLike->new($content);
72 21         26490 my @tocheck=$string->children();
73 21         225 while(@tocheck) {
74 51         7867 my $node=shift(@tocheck);
75 51 100       399 if($node->isa('PPIx::QuoteLike::Token::Interpolation')) {
76 17         66 $content=$node->content();
77 17         206 my $doc=PPI::Document->new(\$content);
78 17 100       33262 foreach my $inner (@{$doc->find('PPI::Token::Operator')||[]}) {
  17         66  
79 4 50       2136 if(my $violation=$self->operatorViolates($inner)) { return $violation }
  4         4571  
80             }
81             }
82             }
83 17         219 return;
84             }
85              
86 0           return;
87             }
88              
89             #-----------------------------------------------------------------------------
90              
91             1;
92              
93             __END__
94              
95             =pod
96              
97             =head1 NAME
98              
99             Perl::Critic::Policy::References::RequireSigils - Only use dereferencing arrows for method calls; use sigils to signal types.
100              
101             =head1 DESCRIPTION
102              
103             Post-conditional and post-fix operators are harder to read and maintain, especially within other operators and functions that work only in infix or prefix/function-call mode. Since certain forms of arrow-dereferencing don't work inside quoted constructs, there can be additional confusion about uniformity of expected behaviors:
104              
105             print "Name: ",$href->{name} # no
106             print "Name: ",$$href{name} # yes
107              
108             print "Item: ",$aref->[1] # no
109             print "Item: ",$$aref[1] # yes
110              
111             my @A=$x->@*; # no
112             my @A=@$x; # yes
113              
114             my $y=$x->method(); # yes
115             print "$x->method();" # invalid code (not checked)
116              
117             print "Name: $href->{name}" # no
118             print "Name: $$href{name}" # yes
119              
120             print "Item: $aref->[1]" # no
121             print "Item: $$aref[1]" # yes
122              
123             =head1 CONFIGURATION
124              
125             Violations within interpolated strings can be disabled by setting C<interpolation>:
126              
127             [References::RequireSigils]
128             interpolation = 0
129              
130             =head1 NOTES
131              
132             Not presently well-tested. There may be some false violations.
133              
134             Inside Quote/QuoteLike expressions, L<String::InterpolatedVariables> will be used in the future to establish consistency.
135              
136             Proposed: Because C<@$x> is a direct casting operation, whereas C<@{ $x }> is a block operator, performance goals may suggest that the latter is a violation of the expected pattern for sigils. In particular it signals "there is a complicated expansion here", when it fact it is just meant as a direct casting operator. Future configuration may support enabling required double sigils where possible.
137              
138             =head1 BUGS
139              
140             This implementation is primarily "Prohibit non-method arrows" at this time.
141              
142             =head1 SEE ALSO
143              
144             See L<Perl::Critic::Policy::References::ProhibitDoubleSigils> to move code away from sigils, but note that does not require postfix dereferencing.
145              
146             =cut