File Coverage

blib/lib/Perl/Critic/Policy/TryTiny/ProhibitExitingSubroutine.pm
Criterion Covered Total %
statement 53 57 92.9
branch 22 30 73.3
condition 13 24 54.1
subroutine 12 13 92.3
pod 4 5 80.0
total 104 129 80.6


line stmt bran cond sub pod time code
1 1     1   206395 use strict;
  1         2  
  1         34  
2 1     1   4 use warnings;
  1         1  
  1         25  
3 1     1   4 use utf8;
  1         1  
  1         6  
4              
5             package Perl::Critic::Policy::TryTiny::ProhibitExitingSubroutine;
6             # ABSTRACT: Ban next/last/return in Try::Tiny blocks
7             $Perl::Critic::Policy::TryTiny::ProhibitExitingSubroutine::VERSION = '0.001';
8 1     1   48 use Readonly;
  1         1  
  1         55  
9 1     1   5 use Perl::Critic::Utils qw( :severities :classification :ppi );
  1         1  
  1         77  
10              
11 1     1   396 use base 'Perl::Critic::Policy';
  1         1  
  1         826  
12              
13             Readonly::Scalar my $DESC => "Using next/last/redo/return in a Try::Tiny block is ambiguous";
14             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?";
15              
16             sub supported_parameters {
17 11     11 0 78291 return ();
18             }
19              
20             sub default_severity {
21 5     5 1 90 return $SEVERITY_HIGH;
22             }
23              
24             sub default_themes {
25 0     0 1 0 return qw(bugs);
26             }
27              
28             sub applies_to {
29 11     11 1 129338 return 'PPI::Token::Word';
30             }
31              
32             sub violates {
33 112     112 1 929 my ($self, $elem, undef) = @_;
34              
35 112 100       185 return if $elem->content ne 'try';
36 11 50       77 return if ! is_function_call($elem);
37              
38 11         2691 my @blocks_to_check;
39              
40 11 100       31 if (my $try_block = $elem->snext_sibling()) {
41 10 50       174 if ($try_block->isa('PPI::Structure::Block')) {
42 10         20 push @blocks_to_check, $try_block;
43             }
44 10         30 my $sib = $try_block->snext_sibling();
45 10 100 66     186 if ($sib and $sib->content eq 'catch' and my $catch_block = $sib->snext_sibling()) {
      66        
46 8 50       212 if ($catch_block->isa('PPI::Structure::Block')) {
47 8         9 push @blocks_to_check, $catch_block;
48             }
49 8         20 $sib = $catch_block->snext_sibling();
50             }
51 10 50 33     185 if ($sib and $sib->content eq 'finally' and my $finally_block = $sib->snext_sibling()) {
      33        
52 0 0       0 if (finally_block->isa('PPI::Structure::Block')) {
53 0         0 push @blocks_to_check, $finally_block;
54             }
55             }
56             }
57              
58 11         90 for my $block_to_check (@blocks_to_check) {
59 15         41 my $violation = $self->_check_block($block_to_check);
60 15 100       961 if (defined($violation)) {
61 5         20 return $violation;
62             }
63             }
64 6         18 return;
65             }
66              
67             sub _check_block {
68 15     15   24 my $self = shift;
69 15         15 my $block = shift;
70              
71 15 100       15 for my $word (@{ $block->find('PPI::Token::Word') || [] }) {
  15         39  
72 28 50       8905 if ($word eq 'return') {
73 0         0 return $self->violation($DESC, $EXPL, $word);
74             }
75              
76 28         404 my $sib = $word->snext_sibling;
77              
78 28 50 66     459 if ($word eq 'next' || $word eq 'redo' || $word eq 'last') {
      66        
79 8 100 33     118 if (! $sib || ! _is_label($sib)) {
80 5         61 return $self->violation($DESC, $EXPL, $word);
81             }
82             }
83             }
84 10         425 return;
85             }
86              
87             sub _is_label {
88 8     8   10 my $element = shift;
89              
90 8 100 66     17 if ($element eq 'if' || $element eq 'unless') {
91 1         12 return 0;
92             }
93              
94 7 100       159 return $element =~ /^[_a-z]+$/i ? 1 : 0;
95             }
96              
97             1;
98              
99             __END__
100              
101             =pod
102              
103             =encoding UTF-8
104              
105             =head1 NAME
106              
107             Perl::Critic::Policy::TryTiny::ProhibitExitingSubroutine - Ban next/last/return in Try::Tiny blocks
108              
109             =head1 VERSION
110              
111             version 0.001
112              
113             =head1 DESCRIPTION
114              
115             Take this code:
116              
117             use Try::Tiny;
118              
119             for my $item (@array) {
120             try {
121             next if $item == 2;
122             # other code
123             }
124             catch {
125             warn $_;
126             };
127             # other code
128             }
129              
130             The next statement will not go the the next iteration of the for-loop, rather,
131             it will exit the "try" block, emitting a warning if warnings are enabled.
132              
133             This is probably not what the developer had intended, so this policy prohibits it.
134              
135             One way to fix this is to use labels:
136              
137             use Try::Tiny;
138              
139             ITEM:
140             for my $item (@array) {
141             try {
142             if ($item == 2) {
143             no warnings 'exiting';
144             next ITEM;
145             }
146             # other code
147             }
148             catch {
149             warn $_;
150             };
151             # other code
152             }
153              
154             =head1 CONFIGURATION
155              
156             This Policy is not configurable except for the standard options.
157              
158             =head1 KNOWN BUGS
159              
160             This policy assumes that L<Try::Tiny> is being used, and doesn't check for
161             whether an alternative like L<TryCatch>.
162              
163             =head1 AUTHOR
164              
165             David D Lowe <flimm@cpan.org>
166              
167             =head1 COPYRIGHT AND LICENSE
168              
169             This software is copyright (c) 2014 by Lokku <cpan@lokku.com>.
170              
171             This is free software; you can redistribute it and/or modify it under
172             the same terms as the Perl 5 programming language system itself.
173              
174             =cut