File Coverage

blib/lib/Perl/Critic/Policy/Subroutines/ProhibitUnusedPrivateSubroutines.pm
Criterion Covered Total %
statement 46 132 34.8
branch 9 94 9.5
condition 3 35 8.5
subroutine 14 23 60.8
pod 4 5 80.0
total 76 289 26.3


line stmt bran cond sub pod time code
1             package Perl::Critic::Policy::Subroutines::ProhibitUnusedPrivateSubroutines;
2              
3 40     40   28613 use 5.010001;
  40         159  
4              
5 40     40   251 use strict;
  40         110  
  40         844  
6 40     40   232 use warnings;
  40         89  
  40         1100  
7              
8 40     40   297 use English qw< $EVAL_ERROR -no_match_vars >;
  40         105  
  40         461  
9 40     40   7252 use List::SomeUtils qw(any);
  40         102  
  40         1932  
10 40     40   306 use Readonly;
  40         129  
  40         2239  
11              
12 40         2174 use Perl::Critic::Utils qw{
13             :characters hashify is_function_call is_method_call :severities
14             $EMPTY $TRUE
15 40     40   310 };
  40         106  
16 40     40   13099 use parent 'Perl::Critic::Policy';
  40         113  
  40         237  
17              
18             our $VERSION = '1.150';
19              
20             #-----------------------------------------------------------------------------
21              
22             Readonly::Scalar my $DESC =>
23             q{Private subroutine/method '%s' declared but not used};
24             Readonly::Scalar my $EXPL => q{Eliminate dead code};
25              
26             Readonly::Hash my %IS_COMMA => hashify( $COMMA, $FATCOMMA );
27              
28             #-----------------------------------------------------------------------------
29              
30             sub supported_parameters {
31             return (
32             {
33 93     93 0 2901 name => 'private_name_regex',
34             description => 'Pattern that determines what a private subroutine is.',
35             default_string => '\b_\w+\b', ## no critic (RequireInterpolationOfMetachars)
36             behavior => 'string',
37             parser => \&_parse_regex_parameter,
38             },
39             {
40             name => 'allow',
41             description =>
42             q<Subroutines matching the private name regex to allow under this policy.>,
43             default_string => $EMPTY,
44             behavior => 'string list',
45             },
46             {
47             name => 'skip_when_using',
48             description =>
49             q<Modules that, if used within a file, will cause the policy to be disabled for this file>,
50             default_string => $EMPTY,
51             behavior => 'string list',
52             },
53             {
54             name => 'allow_name_regex',
55             description =>
56             q<Pattern defining private subroutine names that are always allowed>,
57             default_string => $EMPTY,
58             behavior => 'string',
59             parser => \&_parse_regex_parameter,
60             },
61             );
62             }
63              
64 74     74 1 337 sub default_severity { return $SEVERITY_MEDIUM }
65 74     74 1 304 sub default_themes { return qw( core maintenance certrec ) }
66 30     30 1 96 sub applies_to { return 'PPI::Statement::Sub' }
67              
68             #-----------------------------------------------------------------------------
69              
70             sub _parse_regex_parameter {
71 182     182   571 my ($self, $parameter, $config_string) = @_;
72              
73 182   100     922 $config_string //= $parameter->get_default_string();
74              
75 182         399 my $regex;
76 182 50       368 eval { $regex = qr/$config_string/; 1 } ## no critic (RegularExpressions)
  182         2919  
  182         718  
77             or $self->throw_parameter_value_exception(
78             $parameter,
79             $config_string,
80             undef,
81             "is not a valid regular expression: $EVAL_ERROR",
82             );
83              
84 182         806 $self->__set_parameter_value($parameter, $regex);
85              
86 182         438 return;
87             }
88              
89             #-----------------------------------------------------------------------------
90              
91             sub violates {
92 5     5 1 21 my ( $self, $elem, $document ) = @_;
93              
94 5         11 my @skip_modules = keys %{ $self->{_skip_when_using} };
  5         21  
95 5 50   0   41 return if any { $document->uses_module($_) } @skip_modules;
  0         0  
96              
97             # Not interested in forward declarations, only the real thing.
98 5 50       29 $elem->forward() and return;
99              
100             # Not interested in subs without names.
101 5 50       121 my $name = $elem->name() or return;
102              
103             # If the sub is shoved into someone else's name space, we wimp out.
104 5 50       250 $name =~ m/ :: /smx and return;
105              
106             # If the name is explicitly allowed, we just return (OK).
107 5 50       33 $self->{_allow}{$name} and return;
108              
109             # Allow names that match the 'allow_name_regex' pattern.
110 5 50       20 if ($self->{_allow_name_regex}) {
111 5 50       63 $name =~ m/ \A $self->{_allow_name_regex} \z /smx and return;
112             }
113              
114             # If the name is not an anonymous subroutine according to our definition,
115             # we just return (OK).
116 5 50       55 $name =~ m/ \A $self->{_private_name_regex} \z /smx or return;
117              
118             # If the subroutine is called in the document, just return (OK).
119 0 0         $self->_find_sub_call_in_document( $elem, $document ) and return;
120              
121             # If the subroutine is referred to in the document, just return (OK).
122 0 0         $self->_find_sub_reference_in_document( $elem, $document ) and return;
123              
124             # If the subroutine is used in an overload, just return (OK).
125 0 0         $self->_find_sub_overload_in_document( $elem, $document ) and return;
126              
127             # No uses of subroutine found. Return a violation.
128 0           return $self->violation( sprintf( $DESC, $name ), $EXPL, $elem );
129             }
130              
131              
132             # Basically the spaceship operator for token locations. The arguments are the
133             # two tokens to compare. If either location is unavailable we return undef.
134             sub _compare_token_locations {
135 0     0     my ( $left_token, $right_token ) = @_;
136 0 0         my $left_loc = $left_token->location() or return;
137 0 0         my $right_loc = $right_token->location() or return;
138 0   0       return $left_loc->[0] <=> $right_loc->[0] ||
139             $left_loc->[1] <=> $right_loc->[1];
140             }
141              
142             # Find out if the subroutine defined in $elem is called in $document. Calls
143             # inside the subroutine itself do not count.
144             sub _find_sub_call_in_document {
145 0     0     my ( $self, $elem, $document ) = @_;
146              
147 0           my $start_token = $elem->first_token();
148 0           my $finish_token = $elem->last_token();
149 0           my $name = $elem->name();
150              
151 0 0         if ( my $found = $document->find( 'PPI::Token::Word' ) ) {
152 0           foreach my $usage ( @{ $found } ) {
  0            
153 0 0         $name eq $usage->content() or next;
154 0 0 0       is_function_call( $usage )
155             or is_method_call( $usage )
156             or next;
157 0 0         _compare_token_locations( $usage, $start_token ) < 0
158             and return $TRUE;
159 0 0         _compare_token_locations( $finish_token, $usage ) < 0
160             and return $TRUE;
161             }
162             }
163              
164 0           foreach my $regexp ( _find_regular_expressions( $document ) ) {
165              
166 0 0 0       _compare_token_locations( $regexp, $start_token ) >= 0
167             and _compare_token_locations( $finish_token, $regexp ) >= 0
168             and next;
169 0 0         _find_sub_usage_in_regexp( $name, $regexp, $document )
170             and return $TRUE;
171              
172             }
173              
174 0           return;
175             }
176              
177             # Find analyzable regular expressions in the given document. This means
178             # matches, substitutions, and the qr{} operator.
179             sub _find_regular_expressions {
180 0     0     my ( $document ) = @_;
181              
182 0 0         return ( map { @{ $document->find( $_ ) || [] } } qw{
  0            
  0            
183             PPI::Token::Regexp::Match
184             PPI::Token::Regexp::Substitute
185             PPI::Token::QuoteLike::Regexp
186             } );
187             }
188              
189             # Find out if the subroutine named in $name is called in the given $regexp.
190             # This could happen either by an explicit s/.../.../e, or by interpolation
191             # (i.e. @{[...]} ).
192             sub _find_sub_usage_in_regexp {
193 0     0     my ( $name, $regexp, $document ) = @_;
194              
195 0 0         my $ppix = $document->ppix_regexp_from_element( $regexp ) or return;
196 0 0         $ppix->failures() and return;
197              
198 0 0         foreach my $code ( @{ $ppix->find( 'PPIx::Regexp::Token::Code' ) || [] } ) {
  0            
199 0 0         my $doc = $code->ppi() or next;
200              
201 0 0         foreach my $word ( @{ $doc->find( 'PPI::Token::Word' ) || [] } ) {
  0            
202 0 0         $name eq $word->content() or next;
203 0 0 0       is_function_call( $word )
204             or is_method_call( $word )
205             or next;
206 0           return $TRUE;
207             }
208              
209             }
210              
211 0           return;
212             }
213              
214             # Find out if the subroutine defined in $elem handles an overloaded operator.
215             # We recognize both string literals (the usual form) and words (in case
216             # someone perversely followed the subroutine name by a fat comma). We ignore
217             # the '\&_foo' construction, since _find_sub_reference_in_document() should
218             # find this.
219             sub _find_sub_overload_in_document {
220 0     0     my ( $self, $elem, $document ) = @_;
221              
222 0           my $name = $elem->name();
223              
224 0 0         if ( my $found = $document->find( 'PPI::Statement::Include' ) ) {
225 0           foreach my $usage ( @{ $found } ) {
  0            
226 0 0         'overload' eq $usage->module() or next;
227 0           my $inx;
228 0           foreach my $arg ( _get_include_arguments( $usage ) ) {
229 0 0         $inx++ % 2 or next;
230 0 0         @{ $arg } == 1 or next;
  0            
231 0           my $element = $arg->[0];
232              
233 0 0         if ( $element->isa( 'PPI::Token::Quote' ) ) {
    0          
234 0 0         $element->string() eq $name and return $TRUE;
235             } elsif ( $element->isa( 'PPI::Token::Word' ) ) {
236 0 0         $element->content() eq $name and return $TRUE;
237             }
238             }
239             }
240             }
241              
242 0           return;
243             }
244              
245             # Find things of the form '&_foo'. This includes both references proper (i.e.
246             # '\&foo'), calls using the sigil, and gotos. The latter two do not count if
247             # inside the subroutine itself.
248             sub _find_sub_reference_in_document {
249 0     0     my ( $self, $elem, $document ) = @_;
250              
251 0           my $start_token = $elem->first_token();
252 0           my $finish_token = $elem->last_token();
253 0           my $symbol = q<&> . $elem->name();
254              
255 0 0         if ( my $found = $document->find( 'PPI::Token::Symbol' ) ) {
256 0           foreach my $usage ( @{ $found } ) {
  0            
257 0 0         $symbol eq $usage->content() or next;
258              
259 0           my $prior = $usage->sprevious_sibling();
260 0 0 0       $prior
      0        
261             and $prior->isa( 'PPI::Token::Cast' )
262             and q<\\> eq $prior->content()
263             and return $TRUE;
264              
265 0 0 0       is_function_call( $usage )
      0        
      0        
266             or $prior
267             and $prior->isa( 'PPI::Token::Word' )
268             and 'goto' eq $prior->content()
269             or next;
270              
271 0 0         _compare_token_locations( $usage, $start_token ) < 0
272             and return $TRUE;
273 0 0         _compare_token_locations( $finish_token, $usage ) < 0
274             and return $TRUE;
275             }
276             }
277              
278 0           return;
279             }
280              
281             # Expand the given element, losing any brackets along the way. This is
282             # intended to be used to flatten the argument list of 'use overload'.
283             sub _expand_element {
284 0     0     my ( $element ) = @_;
285             $element->isa( 'PPI::Node' )
286 0 0         and return ( map { _expand_element( $_ ) } $_->children() );
  0            
287 0 0         $element->significant() and return $element;
288 0           return;
289             }
290              
291             # Given an include statement, return its arguments. The return is a flattened
292             # list of lists of tokens, each list of tokens representing an argument.
293             sub _get_include_arguments {
294 0     0     my ($include) = @_;
295              
296             # If there are no arguments, just return. We flatten the list because
297             # someone might use parens to define it.
298 0 0         my @arguments = map { _expand_element( $_ ) } $include->arguments()
  0            
299             or return;
300              
301 0           my @elements;
302 0           my $inx = 0;
303 0           foreach my $element ( @arguments ) {
304 0 0 0       if ( $element->isa( 'PPI::Token::Operator' ) &&
305             $IS_COMMA{$element->content()} ) {
306 0           $inx++;
307             } else {
308 0   0       push @{ $elements[$inx] ||= [] }, $element;
  0            
309             }
310             }
311              
312 0           return @elements;
313             }
314              
315             1;
316              
317             __END__
318              
319             #-----------------------------------------------------------------------------
320              
321             =pod
322              
323             =head1 NAME
324              
325             Perl::Critic::Policy::Subroutines::ProhibitUnusedPrivateSubroutines - Prevent unused private subroutines.
326              
327              
328             =head1 AFFILIATION
329              
330             This Policy is part of the core L<Perl::Critic|Perl::Critic>
331             distribution.
332              
333              
334             =head1 DESCRIPTION
335              
336             By convention Perl authors (like authors in many other languages)
337             indicate private methods and variables by inserting a leading
338             underscore before the identifier. This policy catches such subroutines
339             which are not used in the file which declares them.
340              
341             This module defines a 'use' of a subroutine as a subroutine or method call to
342             it (other than from inside the subroutine itself), a reference to it (i.e.
343             C<< my $foo = \&_foo >>), a C<goto> to it outside the subroutine itself (i.e.
344             C<goto &_foo>), or the use of the subroutine's name as an even-numbered
345             argument to C<< use overload >>.
346              
347              
348             =head1 CONFIGURATION
349              
350             You can define what a private subroutine name looks like by specifying
351             a regular expression for the C<private_name_regex> option in your
352             F<.perlcriticrc>:
353              
354             [Subroutines::ProhibitUnusedPrivateSubroutines]
355             private_name_regex = _(?!_)\w+
356              
357             The above example is a way of saying that subroutines that start with
358             a double underscore are not considered to be private. (Perl::Critic,
359             in its implementation, uses leading double underscores to indicate a
360             distribution-private subroutine -- one that is allowed to be invoked by
361             other Perl::Critic modules, but not by anything outside of
362             Perl::Critic.)
363              
364             You can configure additional subroutines to accept by specifying them
365             in a space-delimited list to the C<allow> option:
366              
367             [Subroutines::ProhibitUnusedPrivateSubroutines]
368             allow = _bar _baz
369              
370             These are added to the default list of exemptions from this policy. So the
371             above allows C<< sub _bar {} >> and C<< sub _baz {} >>, even if they are not
372             referred to in the module that defines them.
373              
374             You can allow a whole class or subroutine names by defining a regular
375             expression that matches allowed names.
376              
377             [Subroutines::ProhibitUnusedPrivateSubroutines]
378             allow_name_regex = _build_\w+
379              
380             You can configure this policy not to check private subroutines declared in a
381             file that uses one or more particular named modules. This allows you to, for
382             example, exclude unused private subroutine checking in classes that are roles.
383              
384             [Subroutines::ProhibitUnusedPrivateSubroutines]
385             skip_when_using = Moose::Role Moo::Role Role::Tiny
386              
387              
388             =head1 HISTORY
389              
390             This policy is derived from
391             L<Perl::Critic::Policy::Subroutines::ProtectPrivateSubs|Perl::Critic::Policy::Subroutines::ProtectPrivateSubs>,
392             which looks at the other side of the problem.
393              
394              
395             =head1 BUGS
396              
397             Does not forbid C<< sub Foo::_foo{} >> because it does not know (and can not
398             assume) what is in the C<Foo> package.
399              
400             Does not respect the scope caused by multiple packages in the same file. For
401             example a file:
402              
403             package Foo;
404             sub _is_private { print "A private sub!"; }
405              
406             package Bar;
407             _is_private();
408              
409             Will not trigger a violation even though C<Foo::_is_private> is not called.
410             Similarly, C<skip_when_using> currently works on a I<file> level, not on a
411             I<package scope> level.
412              
413              
414             =head1 SEE ALSO
415              
416             L<Perl::Critic::Policy::Subroutines::ProtectPrivateSubs|Perl::Critic::Policy::Subroutines::ProtectPrivateSubs>.
417              
418              
419             =head1 AUTHOR
420              
421             Chris Dolan <cdolan@cpan.org>
422              
423             =head1 COPYRIGHT
424              
425             Copyright (c) 2009-2021 Thomas R. Wyant, III.
426              
427             This program is free software; you can redistribute it and/or modify
428             it under the same terms as Perl itself. The full text of this license
429             can be found in the LICENSE file included with this module.
430              
431             =cut
432              
433             # Local Variables:
434             # mode: cperl
435             # cperl-indent-level: 4
436             # fill-column: 78
437             # indent-tabs-mode: nil
438             # c-indentation-style: bsd
439             # End:
440             # ex: set ts=8 sts=4 sw=4 tw=78 ft=perl expandtab shiftround :