File Coverage

blib/lib/Perl/Critic/Policy/Subroutines/ProhibitUnusedPrivateSubroutines.pm
Criterion Covered Total %
statement 131 132 99.2
branch 72 94 76.6
condition 21 35 60.0
subroutine 23 23 100.0
pod 4 5 80.0
total 251 289 86.8


line stmt bran cond sub pod time code
1             package Perl::Critic::Policy::Subroutines::ProhibitUnusedPrivateSubroutines;
2              
3 40     40   28757 use 5.010001;
  40         164  
4              
5 40     40   250 use strict;
  40         138  
  40         865  
6 40     40   242 use warnings;
  40         94  
  40         1203  
7              
8 40     40   258 use English qw< $EVAL_ERROR -no_match_vars >;
  40         91  
  40         288  
9 40     40   6799 use List::SomeUtils qw(any);
  40         111  
  40         2045  
10 40     40   309 use Readonly;
  40         112  
  40         2465  
11              
12 40         2234 use Perl::Critic::Utils qw{
13             :characters hashify is_function_call is_method_call :severities
14             $EMPTY $TRUE
15 40     40   324 };
  40         127  
16 40     40   13076 use parent 'Perl::Critic::Policy';
  40         101  
  40         255  
17              
18             our $VERSION = '1.148';
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 117     117 0 3247 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 81     81 1 395 sub default_severity { return $SEVERITY_MEDIUM }
65 74     74 1 324 sub default_themes { return qw( core maintenance certrec ) }
66 54     54 1 178 sub applies_to { return 'PPI::Statement::Sub' }
67              
68             #-----------------------------------------------------------------------------
69              
70             sub _parse_regex_parameter {
71 230     230   764 my ($self, $parameter, $config_string) = @_;
72              
73 230   100     1303 $config_string //= $parameter->get_default_string();
74              
75 230         573 my $regex;
76 230 50       543 eval { $regex = qr/$config_string/; 1 } ## no critic (RegularExpressions)
  230         4168  
  230         984  
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 230         1123 $self->__set_parameter_value($parameter, $regex);
85              
86 230         754 return;
87             }
88              
89             #-----------------------------------------------------------------------------
90              
91             sub violates {
92 40     40 1 113 my ( $self, $elem, $document ) = @_;
93              
94 40         84 my @skip_modules = keys %{ $self->{_skip_when_using} };
  40         153  
95 40 100   1   255 return if any { $document->uses_module($_) } @skip_modules;
  1         6  
96              
97             # Not interested in forward declarations, only the real thing.
98 39 100       245 $elem->forward() and return;
99              
100             # Not interested in subs without names.
101 38 50       1060 my $name = $elem->name() or return;
102              
103             # If the sub is shoved into someone else's name space, we wimp out.
104 38 100       2319 $name =~ m/ :: /smx and return;
105              
106             # If the name is explicitly allowed, we just return (OK).
107 37 100       182 $self->{_allow}{$name} and return;
108              
109             # Allow names that match the 'allow_name_regex' pattern.
110 34 50       122 if ($self->{_allow_name_regex}) {
111 34 100       288 $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 33 100       338 $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 22 100       95 $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 13 100       52 $self->_find_sub_reference_in_document( $elem, $document ) and return;
123              
124             # If the subroutine is used in an overload, just return (OK).
125 9 100       37 $self->_find_sub_overload_in_document( $elem, $document ) and return;
126              
127             # No uses of subroutine found. Return a violation.
128 7         64 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 20     20   50 my ( $left_token, $right_token ) = @_;
136 20 50       71 my $left_loc = $left_token->location() or return;
137 20 50       334 my $right_loc = $right_token->location() or return;
138 20   33     363 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 22     22   58 my ( $self, $elem, $document ) = @_;
146              
147 22         88 my $start_token = $elem->first_token();
148 22         378 my $finish_token = $elem->last_token();
149 22         457 my $name = $elem->name();
150              
151 22 50       1110 if ( my $found = $document->find( 'PPI::Token::Word' ) ) {
152 22         57 foreach my $usage ( @{ $found } ) {
  22         63  
153 104 100       423 $name eq $usage->content() or next;
154 28 100 100     181 is_function_call( $usage )
155             or is_method_call( $usage )
156             or next;
157 7 100       140 _compare_token_locations( $usage, $start_token ) < 0
158             and return $TRUE;
159 5 100       17 _compare_token_locations( $finish_token, $usage ) < 0
160             and return $TRUE;
161             }
162             }
163              
164 16         114 foreach my $regexp ( _find_regular_expressions( $document ) ) {
165              
166 3 50 33     12 _compare_token_locations( $regexp, $start_token ) >= 0
167             and _compare_token_locations( $finish_token, $regexp ) >= 0
168             and next;
169 3 50       14 _find_sub_usage_in_regexp( $name, $regexp, $document )
170             and return $TRUE;
171              
172             }
173              
174 13         66 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 16     16   47 my ( $document ) = @_;
181              
182 16 100       50 return ( map { @{ $document->find( $_ ) || [] } } qw{
  48         94  
  48         146  
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 3     3   12 my ( $name, $regexp, $document ) = @_;
194              
195 3 50       19 my $ppix = $document->ppix_regexp_from_element( $regexp ) or return;
196 3 50       35801 $ppix->failures() and return;
197              
198 3 50       25 foreach my $code ( @{ $ppix->find( 'PPIx::Regexp::Token::Code' ) || [] } ) {
  3         13  
199 3 50       1152 my $doc = $code->ppi() or next;
200              
201 3 50       39 foreach my $word ( @{ $doc->find( 'PPI::Token::Word' ) || [] } ) {
  3         15  
202 3 50       1638 $name eq $word->content() or next;
203 3 50 33     34 is_function_call( $word )
204             or is_method_call( $word )
205             or next;
206 3         35 return $TRUE;
207             }
208              
209             }
210              
211 0         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 9     9   28 my ( $self, $elem, $document ) = @_;
221              
222 9         30 my $name = $elem->name();
223              
224 9 100       443 if ( my $found = $document->find( 'PPI::Statement::Include' ) ) {
225 2         6 foreach my $usage ( @{ $found } ) {
  2         7  
226 2 50       14 'overload' eq $usage->module() or next;
227 2         74 my $inx;
228 2         15 foreach my $arg ( _get_include_arguments( $usage ) ) {
229 4 100       16 $inx++ % 2 or next;
230 2 50       4 @{ $arg } == 1 or next;
  2         8  
231 2         7 my $element = $arg->[0];
232              
233 2 100       58 if ( $element->isa( 'PPI::Token::Quote' ) ) {
    50          
234 1 50       9 $element->string() eq $name and return $TRUE;
235             } elsif ( $element->isa( 'PPI::Token::Word' ) ) {
236 1 50       5 $element->content() eq $name and return $TRUE;
237             }
238             }
239             }
240             }
241              
242 7         28 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 13     13   33 my ( $self, $elem, $document ) = @_;
250              
251 13         47 my $start_token = $elem->first_token();
252 13         203 my $finish_token = $elem->last_token();
253 13         243 my $symbol = q<&> . $elem->name();
254              
255 13 100       764 if ( my $found = $document->find( 'PPI::Token::Symbol' ) ) {
256 7         27 foreach my $usage ( @{ $found } ) {
  7         20  
257 24 100       96 $symbol eq $usage->content() or next;
258              
259 5         36 my $prior = $usage->sprevious_sibling();
260 5 100 100     172 $prior
      66        
261             and $prior->isa( 'PPI::Token::Cast' )
262             and q<\\> eq $prior->content()
263             and return $TRUE;
264              
265 3 50 33     17 is_function_call( $usage )
      33        
      66        
266             or $prior
267             and $prior->isa( 'PPI::Token::Word' )
268             and 'goto' eq $prior->content()
269             or next;
270              
271 3 100       36 _compare_token_locations( $usage, $start_token ) < 0
272             and return $TRUE;
273 2 100       6 _compare_token_locations( $finish_token, $usage ) < 0
274             and return $TRUE;
275             }
276             }
277              
278 9         49 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 26     26   45 my ( $element ) = @_;
285             $element->isa( 'PPI::Node' )
286 26 100       78 and return ( map { _expand_element( $_ ) } $_->children() );
  24         91  
287 22 100       62 $element->significant() and return $element;
288 12         35 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 2     2   6 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 2 50       17 my @arguments = map { _expand_element( $_ ) } $include->arguments()
  2         104  
299             or return;
300              
301 2         5 my @elements;
302 2         10 my $inx = 0;
303 2         6 foreach my $element ( @arguments ) {
304 10 100 66     44 if ( $element->isa( 'PPI::Token::Operator' ) &&
305             $IS_COMMA{$element->content()} ) {
306 4         54 $inx++;
307             } else {
308 6   50     11 push @{ $elements[$inx] ||= [] }, $element;
  6         29  
309             }
310             }
311              
312 2         8 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 :