File Coverage

blib/lib/Perl/Critic/Policy/Catalyst/ProhibitUnreachableCode.pm
Criterion Covered Total %
statement 85 87 97.7
branch 41 64 64.0
condition 13 21 61.9
subroutine 13 14 92.8
pod 4 5 80.0
total 156 191 81.6


line stmt bran cond sub pod time code
1             package Perl::Critic::Policy::Catalyst::ProhibitUnreachableCode;
2             our $VERSION = '0.02';
3              
4 1     1   399745 use strict;
  1         3  
  1         29  
5 1     1   6 use warnings;
  1         2  
  1         34  
6              
7 1     1   6 use Readonly;
  1         3  
  1         49  
8              
9 1     1   6 use Perl::Critic::Utils qw{ :severities :data_conversion :classification };
  1         2  
  1         52  
10 1     1   416 use base 'Perl::Critic::Policy';
  1         3  
  1         559  
11              
12             Readonly::Array my @CONDITIONALS => qw( if unless foreach while until for );
13             Readonly::Hash my %CONDITIONALS => hashify( @CONDITIONALS );
14              
15             Readonly::Array my @OPERATORS => qw( && || // and or err ? );
16             Readonly::Hash my %OPERATORS => hashify( @OPERATORS );
17              
18             Readonly::Scalar my $DESC => q{Unreachable code};
19             Readonly::Scalar my $EXPL => q{Consider removing it};
20              
21             sub supported_parameters {
22             return(
23             {
24 6     6 0 1203501 name => 'context_methods',
25             description => 'Catalyst context methods which terminate execution',
26             behavior => 'string list',
27             default_string => '',
28             list_always_present_values =>
29             [qw( detach redirect_and_detach )],
30             },
31             {
32             name => 'controller_methods',
33             description => 'Catalyst controller methods which terminate execution',
34             behavior => 'string list',
35             default_string => '',
36             },
37             );
38             }
39              
40 3     3 1 47 sub default_severity { $SEVERITY_HIGH }
41 0     0 1 0 sub default_themes { qw( core bugs certrec catalyst ) }
42 6     6 1 42007 sub applies_to { 'PPI::Token::Word' }
43              
44             sub violates {
45 46     46 1 1140 my ($self, $element) = @_;
46              
47 46 100       119 return if !is_method_call( $element );
48              
49 16         753 my $statement = $element->statement();
50 16 50       252 return if !$statement;
51              
52 16         34 my @context_methods = keys %{ $self->{_context_methods} };
  16         63  
53 16         36 my @controller_methods = keys %{ $self->{_controller_methods} };
  16         47  
54              
55             return unless (
56 16 100 100     82 _is_terminating_context_method( $element, \@context_methods ) or
57             _is_terminating_controller_method( $element, \@controller_methods )
58             );
59              
60 12         45 for my $child ( $statement->schildren() ) {
61 48 50 66     447 return if $child->isa('PPI::Token::Operator') && exists $OPERATORS{$child};
62 48 100 100     422 return if $child->isa('PPI::Token::Word') && exists $CONDITIONALS{$child};
63             }
64              
65 10         36 return $self->_gather_violations($statement);
66             }
67              
68             sub _is_terminating_context_method {
69 16     16   39 my ($element, $methods) = @_;
70              
71 16         29 my $found_method = 0;
72 16         40 foreach my $method (@$methods) {
73 32 100       283 next if $element ne $method;
74 10         160 $found_method = 1;
75 10         32 last;
76             }
77 16 100       134 return 0 if !$found_method;
78              
79 10         32 my $prev = $element->sprevious_sibling();
80 10 50       225 return 0 if !$prev;
81 10 50       27 return 0 if $prev ne '->';
82 10 50       164 return 0 if !$prev->isa('PPI::Token::Operator');
83              
84 10         30 $prev = $prev->sprevious_sibling();
85 10 50       214 return 0 if !$prev;
86 10 50       29 return 0 if $prev ne '$c';
87 10 50       159 return 0 if !$prev->isa('PPI::Token::Symbol');
88              
89 10         35 return 1;
90             }
91              
92             sub _is_terminating_controller_method {
93 6     6   20 my ($element, $methods) = @_;
94              
95 6         13 my $found_method = 0;
96 6         15 foreach my $method (@$methods) {
97 3 50       9 next if $element ne $method;
98 3         42 $found_method = 1;
99 3         8 last;
100             }
101 6 100       30 return 0 if !$found_method;
102              
103 3         15 my $prev = $element->sprevious_sibling();
104 3 50       73 return 0 if !$prev;
105 3 50       11 return 0 if $prev ne '->';
106 3 50       57 return 0 if !$prev->isa('PPI::Token::Operator');
107              
108 3         12 $prev = $prev->sprevious_sibling();
109 3 50       64 return 0 if !$prev;
110 3 50       8 return 0 if $prev ne '$self';
111 3 50       53 return 0 if !$prev->isa('PPI::Token::Symbol');
112              
113             # Save this check for last as its likely the most expensive.
114 3 100       13 return 0 if _find_package_name( $element ) !~ m{::Controller::};
115              
116 2         82 return 1;
117             }
118              
119             sub _find_package_name {
120 3     3   10 my ($element) = @_;
121              
122 3         7 my $original = $element;
123              
124 3         25 while ($element) {
125 16 100       365 if ($element->isa('PPI::Statement::Package')) {
126             # If this package statements is a block package, meaning: package { # stuff in package }
127             # then if we're a descendant of it its our package.
128 3 50       15 return $element->namespace() if $element->ancestor_of( $original );
129              
130             # If we've hit a non-block package then thats our package.
131 3         73 my $blocks = $element->find_any('PPI::Structure::Block');
132 3 50       1046 return $element->namespace() if !$blocks;
133             }
134              
135             # Keep walking backwards until we match the above logic or we get to
136             # the document root (main).
137 13   66     43 $element = $element->sprevious_sibling() || $element->parent();
138             }
139              
140 0         0 return 'main';
141             }
142              
143             sub _gather_violations {
144 10     10   28 my ($self, $statement) = @_;
145              
146 10         24 my @violations = ();
147 10         40 while ( $statement = $statement->snext_sibling() ) {
148 5         170 my @children = $statement->schildren();
149 5 50 33     89 last if @children && $children[0]->isa('PPI::Token::Label');
150 5 100       41 next if $statement->isa('PPI::Statement::Sub');
151 3 50       15 next if $statement->isa('PPI::Statement::End');
152 3 50       19 next if $statement->isa('PPI::Statement::Data');
153 3 50       15 next if $statement->isa('PPI::Statement::Package');
154              
155 3 50 33     18 next if $statement->isa('PPI::Statement::Include') &&
156             $statement->type() ne 'require';
157              
158 3 50 33     22 next if $statement->isa('PPI::Statement::Variable') &&
159             $statement->type() eq 'our';
160              
161 3         24 push @violations, $self->violation( $DESC, $EXPL, $statement );
162             }
163              
164 10         939 return @violations;
165             }
166              
167             1;
168             __END__
169              
170             =encoding utf-8
171              
172             =head1 NAME
173              
174             Perl::Critic::Policy::Catalyst::ProhibitUnreachableCode - Don't write code after an unconditional Catalyst detach.
175              
176             =head1 DESCRIPTION
177              
178             This module was forked from
179             L<Perl::Critic::Policy::ControlStructures::ProhibitUnreachableCode>
180             version C<1.132> and modified to fit.
181              
182             The primary difference is this module looks for these two
183             Catalyst specific bits of code as signifying a terminating statement:
184              
185             $c->detach();
186             $c->redirect_and_detach();
187              
188             The C<redirect_and_detach> context method is available if you are using
189             L<Catalyst::Plugin::RedirectAndDetach>.
190              
191             =head1 PARAMETERS
192              
193             =head2 context_methods
194              
195             By default this policy looks for the C<detach> and C<redirect_and_detach>
196             context methods. You can specify additional context methods to look for
197             with the C<context_methods> parameter. In your C<.perlcriticrc> this
198             would look something like:
199              
200             [Catalyst::ProhibitUnreachableCode]
201             context_methods = my_detaching_method my_other_detaching_method
202              
203             This policy would then consider all of the following lines as
204             terminating statements:
205              
206             $c->detach();
207             $c->redirect_and_detach();
208             $c->my_detaching_method();
209             $c->my_other_detaching_method();
210              
211             =head2 controller_methods
212              
213             Sometimes controllers have in-house methods which call C<detach>, you
214             can specify those:
215              
216             [Catalyst::ProhibitUnreachableCode]
217             controller_methods = foo bar
218              
219             Then this policy would look for any package with C<::Controller::> in
220             its name and would consider the following lines as terminating
221             statements:
222              
223             $self->foo();
224             $self->bar();
225              
226             There are no default methods for this parameter.
227              
228             =head1 SUPPORT
229              
230             Please submit bugs and feature requests to the
231             Perl-Critic-Policy-Catalyst-ProhibitUnreachableCode GitHub issue tracker:
232              
233             L<https://github.com/bluefeet/Perl-Critic-Policy-Catalyst-ProhibitUnreachableCode/issues>
234              
235             =head1 ACKNOWLEDGEMENTS
236              
237             Thanks to L<ZipRecruiter|https://www.ziprecruiter.com/>
238             for encouraging their employees to contribute back to the open
239             source ecosystem. Without their dedication to quality software
240             development this distribution would not exist.
241              
242             =head1 AUTHORS
243              
244             Aran Clary Deltac <bluefeet@gmail.com>
245             Peter Guzis <pguzis@cpan.org>
246              
247             =head1 COPYRIGHT AND LICENSE
248              
249             Copyright (C) 2019 Aran Clary Deltac
250              
251             This program is free software: you can redistribute it and/or modify
252             it under the terms of the GNU General Public License as published by
253             the Free Software Foundation, either version 3 of the License, or
254             (at your option) any later version.
255              
256             This program is distributed in the hope that it will be useful,
257             but WITHOUT ANY WARRANTY; without even the implied warranty of
258             MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
259             GNU General Public License for more details.
260              
261             You should have received a copy of the GNU General Public License
262             along with this program. If not, see L<http://www.gnu.org/licenses/>.
263              
264             =cut
265