File Coverage

blib/lib/Perl/Critic/Policy/Subroutines/ProtectPrivateSubs.pm
Criterion Covered Total %
statement 57 57 100.0
branch 25 28 89.2
condition 14 15 93.3
subroutine 15 15 100.0
pod 4 5 80.0
total 115 120 95.8


line stmt bran cond sub pod time code
1             package Perl::Critic::Policy::Subroutines::ProtectPrivateSubs;
2              
3 40     40   28117 use 5.010001;
  40         165  
4              
5 40     40   266 use strict;
  40         105  
  40         856  
6 40     40   197 use warnings;
  40         145  
  40         1265  
7              
8 40     40   235 use English qw< $EVAL_ERROR -no_match_vars >;
  40         134  
  40         266  
9 40     40   4776 use Readonly;
  40         102  
  40         2353  
10              
11 40         2233 use Perl::Critic::Utils qw<
12             :severities $EMPTY is_function_call is_method_call
13 40     40   307 >;
  40         95  
14 40     40   6325 use parent 'Perl::Critic::Policy';
  40         105  
  40         260  
15              
16             our $VERSION = '1.146';
17              
18             #-----------------------------------------------------------------------------
19              
20             Readonly::Scalar my $DESC => q<Private subroutine/method used>;
21             Readonly::Scalar my $EXPL => q<Use published APIs>;
22              
23             #-----------------------------------------------------------------------------
24              
25             sub supported_parameters {
26             return (
27             {
28 102     102 0 3413 name => 'private_name_regex',
29             description => 'Pattern that determines what a private subroutine is.',
30             default_string => '\b_\w+\b', ## no critic (RequireInterpolationOfMetachars)
31             behavior => 'string',
32             parser => \&_parse_private_name_regex,
33             },
34             {
35             name => 'allow',
36             description =>
37             q<Subroutines matching the private name regex to allow under this policy.>,
38             default_string => $EMPTY,
39             behavior => 'string list',
40             list_always_present_values => [ qw<
41             POSIX::_PC_CHOWN_RESTRICTED
42             POSIX::_PC_LINK_MAX
43             POSIX::_PC_MAX_CANON
44             POSIX::_PC_MAX_INPUT
45             POSIX::_PC_NAME_MAX
46             POSIX::_PC_NO_TRUNC
47             POSIX::_PC_PATH_MAX
48             POSIX::_PC_PIPE_BUF
49             POSIX::_PC_VDISABLE
50             POSIX::_POSIX_ARG_MAX
51             POSIX::_POSIX_CHILD_MAX
52             POSIX::_POSIX_CHOWN_RESTRICTED
53             POSIX::_POSIX_JOB_CONTROL
54             POSIX::_POSIX_LINK_MAX
55             POSIX::_POSIX_MAX_CANON
56             POSIX::_POSIX_MAX_INPUT
57             POSIX::_POSIX_NAME_MAX
58             POSIX::_POSIX_NGROUPS_MAX
59             POSIX::_POSIX_NO_TRUNC
60             POSIX::_POSIX_OPEN_MAX
61             POSIX::_POSIX_PATH_MAX
62             POSIX::_POSIX_PIPE_BUF
63             POSIX::_POSIX_SAVED_IDS
64             POSIX::_POSIX_SSIZE_MAX
65             POSIX::_POSIX_STREAM_MAX
66             POSIX::_POSIX_TZNAME_MAX
67             POSIX::_POSIX_VDISABLE
68             POSIX::_POSIX_VERSION
69             POSIX::_SC_ARG_MAX
70             POSIX::_SC_CHILD_MAX
71             POSIX::_SC_CLK_TCK
72             POSIX::_SC_JOB_CONTROL
73             POSIX::_SC_NGROUPS_MAX
74             POSIX::_SC_OPEN_MAX
75             POSIX::_SC_PAGESIZE
76             POSIX::_SC_SAVED_IDS
77             POSIX::_SC_STREAM_MAX
78             POSIX::_SC_TZNAME_MAX
79             POSIX::_SC_VERSION
80             POSIX::_exit
81             > ],
82             },
83             );
84             }
85              
86 86     86 1 389 sub default_severity { return $SEVERITY_MEDIUM }
87 74     74 1 321 sub default_themes { return qw( core maintenance certrule ) }
88 41     41 1 138 sub applies_to { return 'PPI::Token::Word' }
89              
90             #-----------------------------------------------------------------------------
91              
92             sub _parse_private_name_regex {
93 100     100   534 my ($self, $parameter, $config_string) = @_;
94              
95 100   66     729 $config_string //= $parameter->get_default_string();
96              
97 100         296 my $regex;
98 100 50       316 eval { $regex = qr/$config_string/; 1 } ## no critic (RegularExpressions)
  100         671  
  100         481  
99             or $self->throw_parameter_value_exception(
100             'private_name_regex',
101             $config_string,
102             undef,
103             "is not a valid regular expression: $EVAL_ERROR",
104             );
105              
106 100         690 $self->__set_parameter_value($parameter, $regex);
107              
108 100         364 return;
109             }
110              
111             #-----------------------------------------------------------------------------
112              
113             sub violates {
114 418     418 1 878 my ( $self, $elem, undef ) = @_;
115              
116 418 100       1068 if ( my $prior = $elem->sprevious_sibling() ) {
117 138         3673 my $prior_name = $prior->content();
118 138 100       911 return if $prior_name eq 'package';
119 109 100       326 return if $prior_name eq 'require';
120 108 100       329 return if $prior_name eq 'use';
121             }
122              
123 332 100 100     6092 if (
124             $self->_is_other_pkg_private_function($elem)
125             or $self->_is_other_pkg_private_method($elem)
126             ) {
127 12         60 return $self->violation( $DESC, $EXPL, $elem );
128             }
129              
130 320         1247 return; # ok!
131             }
132              
133             sub _is_other_pkg_private_function {
134 332     332   723 my ( $self, $elem ) = @_;
135              
136 332 100 100     856 return if ! is_method_call($elem) && ! is_function_call($elem);
137              
138 250         956 my $private_name_regex = $self->{_private_name_regex};
139 250         701 my $content = $elem->content();
140             return
141             $content =~ m< \w+::$private_name_regex \z >xms
142             && $content !~ m< \A SUPER::$private_name_regex \z >xms
143 250   100     2865 && ! $self->{_allow}{$content};
144             }
145              
146             sub _is_other_pkg_private_method {
147 326     326   2007 my ( $self, $elem ) = @_;
148              
149 326         705 my $private_name_regex = $self->{_private_name_regex};
150 326         782 my $content = $elem->content();
151              
152             # look for structures like "Some::Package->_foo()"
153 326 100       2506 return if $content !~ m< \A $private_name_regex \z >xms;
154 14 100       47 my $operator = $elem->sprevious_sibling() or return;
155 13 50       306 return if $operator->content() ne q[->];
156              
157 13 50       82 my $package = $operator->sprevious_sibling() or return;
158 13 100       280 return if not $package->isa('PPI::Token::Word');
159              
160             # sometimes the previous sib is a keyword, as in:
161             # shift->_private_method(); This is typically used as
162             # shorthand for "my $self=shift; $self->_private_method()"
163 11 100 100     35 return if $package->content() eq 'shift'
164             or $package->content() eq '__PACKAGE__';
165              
166             # Maybe the user wanted to exempt this explicitly.
167 8 100       97 return if $self->{_allow}{"${package}::$content"};
168              
169 6         47 return 1;
170             }
171              
172             1;
173              
174             __END__
175              
176             #-----------------------------------------------------------------------------
177              
178             =pod
179              
180             =head1 NAME
181              
182             Perl::Critic::Policy::Subroutines::ProtectPrivateSubs - Prevent access to private subs in other packages.
183              
184              
185             =head1 AFFILIATION
186              
187             This Policy is part of the core L<Perl::Critic|Perl::Critic>
188             distribution.
189              
190              
191             =head1 DESCRIPTION
192              
193             By convention Perl authors (like authors in many other languages)
194             indicate private methods and variables by inserting a leading
195             underscore before the identifier. This policy catches attempts to
196             access private variables from outside the package itself.
197              
198             The subroutines in the L<POSIX|POSIX> package which begin with an underscore
199             (e.g. C<POSIX::_POSIX_ARG_MAX>) are not flagged as errors by this
200             policy.
201              
202              
203             =head1 CONFIGURATION
204              
205             You can define what a private subroutine name looks like by specifying
206             a regular expression for the C<private_name_regex> option in your
207             F<.perlcriticrc>:
208              
209             [Subroutines::ProtectPrivateSubs]
210             private_name_regex = _(?!_)\w+
211              
212             The above example is a way of saying that subroutines that start with
213             a double underscore are not considered to be private. (Perl::Critic,
214             in its implementation, uses leading double underscores to indicate a
215             distribution-private subroutine-- one that is allowed to be invoked by
216             other Perl::Critic modules, but not by anything outside of
217             Perl::Critic.)
218              
219             You can configure additional subroutines to accept by specifying them
220             in a space-delimited list to the C<allow> option:
221              
222             [Subroutines::ProtectPrivateSubs]
223             allow = FOO::_bar FOO::_baz
224              
225             These are added to the default list of exemptions from this policy.
226             Allowing a subroutine also allows the corresponding method call. So
227             C<< FOO::_bar >> in the above example allows both C<< FOO::_bar() >>
228             and C<< FOO->_bar() >>.
229              
230              
231             =head1 HISTORY
232              
233             This policy is inspired by a similar test in L<B::Lint|B::Lint>.
234              
235              
236             =head1 BUGS
237              
238             Doesn't forbid C<< $pkg->_foo() >> because it can't tell the
239             difference between that and C<< $self->_foo() >>.
240              
241              
242             =head1 SEE ALSO
243              
244             L<Perl::Critic::Policy::Variables::ProtectPrivateVars|Perl::Critic::Policy::Variables::ProtectPrivateVars>
245              
246              
247             =head1 AUTHOR
248              
249             Chris Dolan <cdolan@cpan.org>
250              
251             =head1 COPYRIGHT
252              
253             Copyright (c) 2006-2011 Chris Dolan.
254              
255             This program is free software; you can redistribute it and/or modify
256             it under the same terms as Perl itself. The full text of this license
257             can be found in the LICENSE file included with this module.
258              
259             =cut
260              
261             # Local Variables:
262             # mode: cperl
263             # cperl-indent-level: 4
264             # fill-column: 78
265             # indent-tabs-mode: nil
266             # c-indentation-style: bsd
267             # End:
268             # ex: set ts=8 sts=4 sw=4 tw=78 ft=perl expandtab shiftround :