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.003';
3 1     1   208907 use strict;
  1         3  
  1         26  
4 1     1   5 use warnings;
  1         2  
  1         19  
5 1     1   5 use utf8;
  1         2  
  1         6  
6              
7             # ABSTRACT: Ban next/last/return in Try::Tiny blocks
8              
9 1     1   23 use Readonly;
  1         2  
  1         58  
10 1     1   6 use Perl::Critic::Utils qw( :severities :classification :ppi );
  1         2  
  1         60  
11              
12 1     1   337 use base 'Perl::Critic::Policy';
  1         2  
  1         477  
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 129475 return ();
19             }
20              
21             sub default_severity {
22 6     6 1 108 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 284620 my $self = shift;
31 15         38 my $document = shift;
32              
33             return $document->find_any(sub {
34 109     109   1536 my $element = $_[1];
35 109 100       432 return 0 if ! $element->isa('PPI::Statement::Include');
36 13         65 my @children = grep { $_->significant } $element->children;
  52         223  
37 13 50 33     174 if ($children[1] && $children[1]->isa('PPI::Token::Word') && $children[1] eq 'Try::Tiny') {
      33        
38 13         323 return 1;
39             }
40 0         0 return 0;
41 15         157 });
42             }
43              
44             sub applies_to {
45 13     13 1 547 return 'PPI::Token::Word';
46             }
47              
48             sub violates {
49 137     137 1 1677 my ($self, $elem, undef) = @_;
50              
51 137 100       351 return if $elem->content ne 'try';
52 14 50       131 return if ! is_function_call($elem);
53              
54 14         5767 my @blocks_to_check;
55              
56 14 50       49 if (my $try_block = $elem->snext_sibling()) {
57 14 50       468 if ($try_block->isa('PPI::Structure::Block')) {
58 14         44 push @blocks_to_check, $try_block;
59             }
60 14         66 my $sib = $try_block->snext_sibling();
61 14 100 66     899 if ($sib and $sib->content eq 'catch' and my $catch_block = $sib->snext_sibling()) {
      66        
62 8 50       475 if ($catch_block->isa('PPI::Structure::Block')) {
63 8         18 push @blocks_to_check, $catch_block;
64             }
65 8         24 $sib = $catch_block->snext_sibling();
66             }
67 14 50 33     553 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         114 for my $block_to_check (@blocks_to_check) {
75 19         68 my $violation = $self->_check_block($block_to_check);
76 19 100       66 if (defined($violation)) {
77 6         28 return $violation;
78             }
79             }
80 8         33 return;
81             }
82              
83             sub _check_block {
84 19     19   43 my $self = shift;
85 19         44 my $block = shift;
86              
87 19         45 my $violation;
88              
89             my $wanted;
90             $wanted = sub {
91 380     380   5011 my ($parent, $element, $in_for_loop, $in_sub_block) = @_;
92 380   100     1542 $in_for_loop //= 0;
93 380   100     1433 $in_sub_block //= 0;
94              
95 380 100       3100 if ($element->isa('PPI::Statement::Compound')) {
    100          
    100          
96 5 100 66     23 if ( $element->type eq 'for' || $element->type eq 'foreach') {
97 2         271 my ($subblock) = grep { $_->isa('PPI::Structure::Block') } $element->schildren;
  10         140  
98 2         19 $subblock->find_any(sub { $wanted->(@_, 1, $in_sub_block) });
  68         1339  
99 2         44 return;
100             }
101             }
102             elsif ($element->isa("PPI::Structure::Block")) {
103 6         32 my $prev_sib = $element->sprevious_sibling;
104 6 100 66     525 if ($prev_sib && $prev_sib->isa("PPI::Token::Word") && $prev_sib eq 'sub') {
      100        
105 2         55 $element->find_any(sub { $wanted->(@_, $in_for_loop, 1) });
  50         1071  
106 2         41 return;
107             }
108             }
109             elsif ($element->isa('PPI::Token::Word')) {
110 45 100 100     165 if ($element eq 'return' && ! $in_sub_block) {
111 1         31 $violation = $self->violation($DESC, $EXPL, $element);
112 1         317 return 1;
113             }
114              
115 44         863 my $sib = $element->snext_sibling;
116              
117 44 100 66     1878 if ($element eq 'next' || $element eq 'redo' || $element eq 'last') {
      66        
118 10 100 100     225 if (! $in_for_loop && (! $sib || ! _is_label($sib))) {
119 5         72 $violation = $self->violation($DESC, $EXPL, $element);
120 5         1290 return 1;
121             }
122             }
123             }
124 19         127 };
125 19         83 $block->find_any($wanted);
126              
127 19         302 return $violation;
128             }
129              
130             sub _is_label {
131 8     8   23 my $element = shift;
132              
133 8 100 66     26 if ($element eq 'if' || $element eq 'unless') {
134 1         17 return 0;
135             }
136              
137 7 100       224 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.003
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             This policy assumes that L<Try::Tiny> is being used, and it doesn't run if it
198             can't find it being imported.
199              
200             =head1 CONFIGURATION
201              
202             This Policy is not configurable except for the standard options.
203              
204             =head1 AUTHOR
205              
206             David D Lowe <flimm@cpan.org>
207              
208             =head1 COPYRIGHT AND LICENSE
209              
210             This software is copyright (c) 2017 by Lokku <cpan@lokku.com>.
211              
212             This is free software; you can redistribute it and/or modify it under
213             the same terms as the Perl 5 programming language system itself.
214              
215             =cut