File Coverage

blib/lib/Perl/Critic/Policy/Catalyst/ProhibitUnreachableCode.pm
Criterion Covered Total %
statement 87 89 97.7
branch 41 64 64.0
condition 13 21 61.9
subroutine 14 15 93.3
pod 4 5 80.0
total 159 194 81.9


line stmt bran cond sub pod time code
1             package Perl::Critic::Policy::Catalyst::ProhibitUnreachableCode;
2 1     1   372521 use 5.008001;
  1         4  
3 1     1   5 use strict;
  1         3  
  1         21  
4 1     1   5 use warnings;
  1         3  
  1         40  
5             our $VERSION = '0.01';
6              
7 1     1   6 use Readonly;
  1         3  
  1         91  
8              
9 1     1   8 use Perl::Critic::Utils qw{ :severities :data_conversion :classification };
  1         2  
  1         68  
10 1     1   402 use base 'Perl::Critic::Policy';
  1         3  
  1         590  
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 1205899 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 44 sub default_severity { $SEVERITY_HIGH }
41 0     0 1 0 sub default_themes { qw( core bugs certrec catalyst ) }
42 6     6 1 41485 sub applies_to { 'PPI::Token::Word' }
43              
44             sub violates {
45 46     46 1 1104 my ($self, $element) = @_;
46              
47 46 100       120 return if !is_method_call( $element );
48              
49 16         726 my $statement = $element->statement();
50 16 50       281 return if !$statement;
51              
52 16         35 my @context_methods = keys %{ $self->{_context_methods} };
  16         66  
53 16         52 my @controller_methods = keys %{ $self->{_controller_methods} };
  16         45  
54              
55             return unless (
56 16 100 100     46 _is_terminating_context_method( $element, \@context_methods ) or
57             _is_terminating_controller_method( $element, \@controller_methods )
58             );
59              
60 12         41 for my $child ( $statement->schildren() ) {
61 48 50 66     456 return if $child->isa('PPI::Token::Operator') && exists $OPERATORS{$child};
62 48 100 100     360 return if $child->isa('PPI::Token::Word') && exists $CONDITIONALS{$child};
63             }
64              
65 10         33 return $self->_gather_violations($statement);
66             }
67              
68             sub _is_terminating_context_method {
69 16     16   38 my ($element, $methods) = @_;
70              
71 16         32 my $found_method = 0;
72 16         40 foreach my $method (@$methods) {
73 23 100       155 next if $element ne $method;
74 10         152 $found_method = 1;
75 10         21 last;
76             }
77 16 100       124 return 0 if !$found_method;
78              
79 10         27 my $prev = $element->sprevious_sibling();
80 10 50       212 return 0 if !$prev;
81 10 50       33 return 0 if $prev ne '->';
82 10 50       177 return 0 if !$prev->isa('PPI::Token::Operator');
83              
84 10         30 $prev = $prev->sprevious_sibling();
85 10 50       192 return 0 if !$prev;
86 10 50       24 return 0 if $prev ne '$c';
87 10 50       182 return 0 if !$prev->isa('PPI::Token::Symbol');
88              
89 10         31 return 1;
90             }
91              
92             sub _is_terminating_controller_method {
93 6     6   14 my ($element, $methods) = @_;
94              
95 6         13 my $found_method = 0;
96 6         15 foreach my $method (@$methods) {
97 3 50       8 next if $element ne $method;
98 3         45 $found_method = 1;
99 3         7 last;
100             }
101 6 100       29 return 0 if !$found_method;
102              
103 3         9 my $prev = $element->sprevious_sibling();
104 3 50       65 return 0 if !$prev;
105 3 50       8 return 0 if $prev ne '->';
106 3 50       56 return 0 if !$prev->isa('PPI::Token::Operator');
107              
108 3         9 $prev = $prev->sprevious_sibling();
109 3 50       59 return 0 if !$prev;
110 3 50       9 return 0 if $prev ne '$self';
111 3 50       51 return 0 if !$prev->isa('PPI::Token::Symbol');
112              
113             # Save this check for last as its likely the most expensive.
114 3 100       10 return 0 if _find_package_name( $element ) !~ m{::Controller::};
115              
116 2         88 return 1;
117             }
118              
119             sub _find_package_name {
120 3     3   8 my ($element) = @_;
121              
122 3         6 my $original = $element;
123              
124 3         14 while ($element) {
125 16 100       324 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       13 return $element->namespace() if $element->ancestor_of( $original );
129              
130             # If we've hit a non-block package then thats our package.
131 3         64 my $blocks = $element->find_any('PPI::Structure::Block');
132 3 50       959 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     39 $element = $element->sprevious_sibling() || $element->parent();
138             }
139              
140 0         0 return 'main';
141             }
142              
143             sub _gather_violations {
144 10     10   26 my ($self, $statement) = @_;
145              
146 10         20 my @violations = ();
147 10         37 while ( $statement = $statement->snext_sibling() ) {
148 5         143 my @children = $statement->schildren();
149 5 50 33     85 last if @children && $children[0]->isa('PPI::Token::Label');
150 5 100       33 next if $statement->isa('PPI::Statement::Sub');
151 3 50       15 next if $statement->isa('PPI::Statement::End');
152 3 50       17 next if $statement->isa('PPI::Statement::Data');
153 3 50       15 next if $statement->isa('PPI::Statement::Package');
154              
155 3 50 33     20 next if $statement->isa('PPI::Statement::Include') &&
156             $statement->type() ne 'require';
157              
158 3 50 33     18 next if $statement->isa('PPI::Statement::Variable') &&
159             $statement->type() eq 'our';
160              
161 3         21 push @violations, $self->violation( $DESC, $EXPL, $statement );
162             }
163              
164 10         893 return @violations;
165             }
166              
167             1;
168             __END__
169              
170             =encoding utf-8
171              
172             =head1 NAME
173              
174             Perl::Critic::Policy::Catalyst::ProhibitUnreachableCode -
175             Don't write code after an unconditional Catalyst detach.
176              
177             =head1 DESCRIPTION
178              
179             This module was forked from
180             L<Perl::Critic::Policy::ControlStructures::ProhibitUnreachableCode>
181             version C<1.132> and modified to fit.
182              
183             The primary difference is this module looks for these two
184             Catalyst specific bits of code as signifying a terminating statement:
185              
186             $c->detach();
187             $c->redirect_and_detach();
188              
189             The C<redirect_and_detach> context method is available if you are using
190             L<Catalyst::Plugin::RedirectAndDetach>.
191              
192             =head1 PARAMETERS
193              
194             =head2 context_methods
195              
196             By default this policy looks for the C<detach> and C<redirect_and_detach>
197             context methods. You can specify additional context methods to look for
198             with the C<context_methods> parameter. In your C<.perlcriticrc> this
199             would look something like:
200              
201             [Catalyst::ProhibitUnreachableCode]
202             context_methods = my_detaching_method my_other_detaching_method
203              
204             This policy would then consider all of the following lines as
205             terminating statements:
206              
207             $c->detach();
208             $c->redirect_and_detach();
209             $c->my_detaching_method();
210             $c->my_other_detaching_method();
211              
212             =head2 controller_methods
213              
214             Sometimes controllers have in-house methods which call C<detach>, you
215             can specify those:
216              
217             [Catalyst::ProhibitUnreachableCode]
218             controller_methods = foo bar
219              
220             Then this policy would look for any package with C<::Controller::> in
221             its name and would consider the following lines as terminating
222             statements:
223              
224             $self->foo();
225             $self->bar();
226              
227             There are no default methods for this parameter.
228              
229             =head1 SUPPORT
230              
231             Please submit bugs and feature requests to the
232             Perl-Critic-Policy-Catalyst-ProhibitUnreachableCode GitHub issue tracker:
233              
234             L<https://github.com/bluefeet/Perl-Critic-Policy-Catalyst-ProhibitUnreachableCode/issues>
235              
236             =head1 AUTHORS
237              
238             Aran Clary Deltac <bluefeet@gmail.com>
239             Peter Guzis <pguzis@cpan.org>
240              
241             =head1 ACKNOWLEDGEMENTS
242              
243             Thanks to L<ZipRecruiter|https://www.ziprecruiter.com/>
244             for encouraging their employees to contribute back to the open
245             source ecosystem. Without their dedication to quality software
246             development this distribution would not exist.
247              
248             =head1 LICENSE
249              
250             This library is free software; you can redistribute it and/or modify
251             it under the same terms as Perl itself.
252              
253             =cut
254              
255             # Local Variables:
256             # mode: cperl
257             # cperl-indent-level: 4
258             # fill-column: 78
259             # indent-tabs-mode: nil
260             # c-indentation-style: bsd
261             # End:
262             # ex: set ts=8 sts=4 sw=4 tw=78 ft=perl expandtab shiftround :