File Coverage

blib/lib/Perl/Critic/Policy/TryTiny/ProhibitExitingSubroutine.pm
Criterion Covered Total %
statement 81 85 95.2
branch 34 42 80.9
condition 31 46 67.3
subroutine 15 16 93.7
pod 5 6 83.3
total 166 195 85.1


line stmt bran cond sub pod time code
1             package Perl::Critic::Policy::TryTiny::ProhibitExitingSubroutine;
2             $Perl::Critic::Policy::TryTiny::ProhibitExitingSubroutine::VERSION = '0.002';
3 1     1   211837 use strict;
  1         2  
  1         32  
4 1     1   4 use warnings;
  1         0  
  1         18  
5 1     1   4 use utf8;
  1         2  
  1         7  
6              
7             # ABSTRACT: Ban next/last/return in Try::Tiny blocks
8              
9 1     1   16 use Readonly;
  1         1  
  1         40  
10 1     1   4 use Perl::Critic::Utils qw( :severities :classification :ppi );
  1         1  
  1         64  
11              
12 1     1   359 use base 'Perl::Critic::Policy';
  1         2  
  1         708  
13              
14             Readonly::Scalar my $DESC => "Using next/last/redo/return in a Try::Tiny block is ambiguous";
15             Readonly::Scalar my $EXPL => "Using next/last/redo without a label or using return in a Try::Tiny block is ambiguous, did you intend to exit out of the try/catch/finally block or the surrounding block?";
16              
17             sub supported_parameters {
18 15     15 0 93174 return ();
19             }
20              
21             sub default_severity {
22 6     6 1 65 return $SEVERITY_HIGH;
23             }
24              
25             sub default_themes {
26 0     0 1 0 return qw(bugs);
27             }
28              
29             sub prepare_to_scan_document {
30 15     15 1 173115 my $self = shift;
31 15         32 my $document = shift;
32              
33             return $document->find_any(sub {
34 109     109   1006 my $element = $_[1];
35 109 100       421 return 0 if ! $element->isa('PPI::Statement::Include');
36 13         55 my @children = grep { $_->significant } $element->children;
  52         158  
37 13 50 33     200 if ($children[1] && $children[1]->isa('PPI::Token::Word') && $children[1] eq 'Try::Tiny') {
      33        
38 13         262 return 1;
39             }
40 0         0 return 0;
41 15         117 });
42             }
43              
44             sub applies_to {
45 13     13 1 380 return 'PPI::Token::Word';
46             }
47              
48             sub violates {
49 137     137 1 1121 my ($self, $elem, undef) = @_;
50              
51 137 100       196 return if $elem->content ne 'try';
52 14 50       135 return if ! is_function_call($elem);
53              
54 14         3552 my @blocks_to_check;
55              
56 14 50       38 if (my $try_block = $elem->snext_sibling()) {
57 14 50       222 if ($try_block->isa('PPI::Structure::Block')) {
58 14         27 push @blocks_to_check, $try_block;
59             }
60 14         43 my $sib = $try_block->snext_sibling();
61 14 100 66     232 if ($sib and $sib->content eq 'catch' and my $catch_block = $sib->snext_sibling()) {
      66        
62 8 50       248 if ($catch_block->isa('PPI::Structure::Block')) {
63 8         18 push @blocks_to_check, $catch_block;
64             }
65 8         13 $sib = $catch_block->snext_sibling();
66             }
67 14 50 33     215 if ($sib and $sib->content eq 'finally' and my $finally_block = $sib->snext_sibling()) {
      33        
68 0 0       0 if (finally_block->isa('PPI::Structure::Block')) {
69 0         0 push @blocks_to_check, $finally_block;
70             }
71             }
72             }
73              
74 14         104 for my $block_to_check (@blocks_to_check) {
75 19         55 my $violation = $self->_check_block($block_to_check);
76 19 100       52 if (defined($violation)) {
77 6         17 return $violation;
78             }
79             }
80 8         22 return;
81             }
82              
83             sub _check_block {
84 19     19   27 my $self = shift;
85 19         27 my $block = shift;
86              
87 19         18 my $violation;
88              
89             my $wanted;
90             $wanted = sub {
91 380     380   2900 my ($parent, $element, $in_for_loop, $in_sub_block) = @_;
92 380   100     802 $in_for_loop //= 0;
93 380   100     683 $in_sub_block //= 0;
94              
95 380 100       3257 if ($element->isa('PPI::Statement::Compound')) {
    100          
    100          
96 5 100 66     16 if ( $element->type eq 'for' || $element->type eq 'foreach') {
97 2         151 my ($subblock) = grep { $_->isa('PPI::Structure::Block') } $element->schildren;
  10         55  
98 2         11 $subblock->find_any(sub { $wanted->(@_, 1, $in_sub_block) });
  68         681  
99 2         23 return undef;
100             }
101             }
102             elsif ($element->isa("PPI::Structure::Block")) {
103 6         18 my $prev_sib = $element->sprevious_sibling;
104 6 100 66     155 if ($prev_sib && $prev_sib->isa("PPI::Token::Word") && $prev_sib eq 'sub') {
      100        
105 2         44 $element->find_any(sub { $wanted->(@_, $in_for_loop, 1) });
  50         759  
106 2         32 return undef;
107             }
108             }
109             elsif ($element->isa('PPI::Token::Word')) {
110 45 100 100     100 if ($element eq 'return' && ! $in_sub_block) {
111 1         20 $violation = $self->violation($DESC, $EXPL, $element);
112 1         152 return 1;
113             }
114              
115 44         551 my $sib = $element->snext_sibling;
116              
117 44 100 66     686 if ($element eq 'next' || $element eq 'redo' || $element eq 'last') {
      66        
118 10 100 100     153 if (! $in_for_loop && (! $sib || ! _is_label($sib))) {
119 5         54 $violation = $self->violation($DESC, $EXPL, $element);
120 5         894 return 1;
121             }
122             }
123             }
124 19         118 };
125 19         51 $block->find_any($wanted);
126              
127 19         177 return $violation;
128             }
129              
130             sub _is_label {
131 8     8   10 my $element = shift;
132              
133 8 100 66     14 if ($element eq 'if' || $element eq 'unless') {
134 1         13 return 0;
135             }
136              
137 7 100       196 return $element =~ /^[_a-z]+$/i ? 1 : 0;
138             }
139              
140             1;
141              
142             __END__
143              
144             =pod
145              
146             =encoding UTF-8
147              
148             =head1 NAME
149              
150             Perl::Critic::Policy::TryTiny::ProhibitExitingSubroutine - Ban next/last/return in Try::Tiny blocks
151              
152             =head1 VERSION
153              
154             version 0.002
155              
156             =head1 DESCRIPTION
157              
158             Take this code:
159              
160             use Try::Tiny;
161              
162             for my $item (@array) {
163             try {
164             next if $item == 2;
165             # other code
166             }
167             catch {
168             warn $_;
169             };
170             # other code
171             }
172              
173             The next statement will not go to the next iteration of the for-loop, rather,
174             it will exit the try block, emitting a warning if warnings are enabled.
175              
176             This is probably not what the developer had intended, so this policy prohibits it.
177              
178             One way to fix this is to use labels:
179              
180             use Try::Tiny;
181              
182             ITEM:
183             for my $item (@array) {
184             try {
185             if ($item == 2) {
186             no warnings 'exiting';
187             next ITEM;
188             }
189             # other code
190             }
191             catch {
192             warn $_;
193             };
194             # other code
195             }
196              
197             =head1 CONFIGURATION
198              
199             This Policy is not configurable except for the standard options.
200              
201             =head1 KNOWN BUGS
202              
203             This policy assumes that L<Try::Tiny> is being used, and it doesn't run if it
204             can't find it being imported.
205              
206             =head1 AUTHOR
207              
208             David D Lowe <flimm@cpan.org>
209              
210             =head1 COPYRIGHT AND LICENSE
211              
212             This software is copyright (c) 2015 by Lokku <cpan@lokku.com>.
213              
214             This is free software; you can redistribute it and/or modify it under
215             the same terms as the Perl 5 programming language system itself.
216              
217             =cut