File Coverage

blib/lib/Perl/Critic/Policy/Subroutines/ProtectPrivateSubs.pm
Criterion Covered Total %
statement 55 55 100.0
branch 21 24 87.5
condition 14 15 93.3
subroutine 15 15 100.0
pod 4 5 80.0
total 109 114 95.6


line stmt bran cond sub pod time code
1             package Perl::Critic::Policy::Subroutines::ProtectPrivateSubs;
2              
3 40     40   28084 use 5.010001;
  40         160  
4              
5 40     40   250 use strict;
  40         93  
  40         865  
6 40     40   196 use warnings;
  40         111  
  40         1269  
7              
8 40     40   247 use English qw< $EVAL_ERROR -no_match_vars >;
  40         97  
  40         233  
9 40     40   4430 use Readonly;
  40         112  
  40         2669  
10              
11 40         2167 use Perl::Critic::Utils qw<
12             :severities $EMPTY is_function_call is_method_call hashify
13 40     40   296 >;
  40         119  
14 40     40   6543 use parent 'Perl::Critic::Policy';
  40         97  
  40         246  
15              
16             our $VERSION = '1.148';
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 3254 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 413 sub default_severity { return $SEVERITY_MEDIUM }
87 74     74 1 426 sub default_themes { return qw( core maintenance certrule ) }
88 41     41 1 161 sub applies_to { return 'PPI::Token::Word' }
89              
90             #-----------------------------------------------------------------------------
91              
92             sub _parse_private_name_regex {
93 100     100   513 my ($self, $parameter, $config_string) = @_;
94              
95 100   66     809 $config_string //= $parameter->get_default_string();
96              
97 100         279 my $regex;
98 100 50       310 eval { $regex = qr/$config_string/; 1 } ## no critic (RegularExpressions)
  100         714  
  100         463  
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         639 $self->__set_parameter_value($parameter, $regex);
107              
108 100         376 return;
109             }
110              
111             #-----------------------------------------------------------------------------
112              
113             sub violates {
114 418     418 1 931 my ( $self, $elem, undef ) = @_;
115              
116 418 100       1097 if ( my $prior = $elem->sprevious_sibling() ) {
117 138         3666 state $exceptions = { hashify( qw( package require use ) ) };
118 138 100       366 return if exists $exceptions->{ $prior->content() };
119             }
120              
121 332 100 100     6497 if (
122             $self->_is_other_pkg_private_function($elem)
123             or $self->_is_other_pkg_private_method($elem)
124             ) {
125 12         58 return $self->violation( $DESC, $EXPL, $elem );
126             }
127              
128 320         1171 return; # ok!
129             }
130              
131             sub _is_other_pkg_private_function {
132 332     332   670 my ( $self, $elem ) = @_;
133              
134 332 100 100     852 return if ! is_method_call($elem) && ! is_function_call($elem);
135              
136 250         1039 my $private_name_regex = $self->{_private_name_regex};
137 250         717 my $content = $elem->content();
138             return
139             $content =~ m< \w+::$private_name_regex \z >xms
140             && $content !~ m< \A SUPER::$private_name_regex \z >xms
141 250   100     2991 && ! $self->{_allow}{$content};
142             }
143              
144             sub _is_other_pkg_private_method {
145 326     326   2137 my ( $self, $elem ) = @_;
146              
147 326         624 my $private_name_regex = $self->{_private_name_regex};
148 326         758 my $content = $elem->content();
149              
150             # look for structures like "Some::Package->_foo()"
151 326 100       2900 return if $content !~ m< \A $private_name_regex \z >xms;
152 14 100       44 my $operator = $elem->sprevious_sibling() or return;
153 13 50       364 return if $operator->content() ne q[->];
154              
155 13 50       83 my $package = $operator->sprevious_sibling() or return;
156 13 100       292 return if not $package->isa('PPI::Token::Word');
157              
158             # sometimes the previous sib is a keyword, as in:
159             # shift->_private_method(); This is typically used as
160             # shorthand for "my $self=shift; $self->_private_method()"
161 11 100 100     32 return if $package->content() eq 'shift'
162             or $package->content() eq '__PACKAGE__';
163              
164             # Maybe the user wanted to exempt this explicitly.
165 8 100       89 return if $self->{_allow}{"${package}::$content"};
166              
167 6         54 return 1;
168             }
169              
170             1;
171              
172             __END__
173              
174             #-----------------------------------------------------------------------------
175              
176             =pod
177              
178             =head1 NAME
179              
180             Perl::Critic::Policy::Subroutines::ProtectPrivateSubs - Prevent access to private subs in other packages.
181              
182              
183             =head1 AFFILIATION
184              
185             This Policy is part of the core L<Perl::Critic|Perl::Critic>
186             distribution.
187              
188              
189             =head1 DESCRIPTION
190              
191             By convention Perl authors (like authors in many other languages)
192             indicate private methods and variables by inserting a leading
193             underscore before the identifier. This policy catches attempts to
194             access private variables from outside the package itself.
195              
196             The subroutines in the L<POSIX|POSIX> package which begin with an underscore
197             (e.g. C<POSIX::_POSIX_ARG_MAX>) are not flagged as errors by this
198             policy.
199              
200              
201             =head1 CONFIGURATION
202              
203             You can define what a private subroutine name looks like by specifying
204             a regular expression for the C<private_name_regex> option in your
205             F<.perlcriticrc>:
206              
207             [Subroutines::ProtectPrivateSubs]
208             private_name_regex = _(?!_)\w+
209              
210             The above example is a way of saying that subroutines that start with
211             a double underscore are not considered to be private. (Perl::Critic,
212             in its implementation, uses leading double underscores to indicate a
213             distribution-private subroutine-- one that is allowed to be invoked by
214             other Perl::Critic modules, but not by anything outside of
215             Perl::Critic.)
216              
217             You can configure additional subroutines to accept by specifying them
218             in a space-delimited list to the C<allow> option:
219              
220             [Subroutines::ProtectPrivateSubs]
221             allow = FOO::_bar FOO::_baz
222              
223             These are added to the default list of exemptions from this policy.
224             Allowing a subroutine also allows the corresponding method call. So
225             C<< FOO::_bar >> in the above example allows both C<< FOO::_bar() >>
226             and C<< FOO->_bar() >>.
227              
228              
229             =head1 HISTORY
230              
231             This policy is inspired by a similar test in L<B::Lint|B::Lint>.
232              
233              
234             =head1 BUGS
235              
236             Doesn't forbid C<< $pkg->_foo() >> because it can't tell the
237             difference between that and C<< $self->_foo() >>.
238              
239              
240             =head1 SEE ALSO
241              
242             L<Perl::Critic::Policy::Variables::ProtectPrivateVars|Perl::Critic::Policy::Variables::ProtectPrivateVars>
243              
244              
245             =head1 AUTHOR
246              
247             Chris Dolan <cdolan@cpan.org>
248              
249             =head1 COPYRIGHT
250              
251             Copyright (c) 2006-2011 Chris Dolan.
252              
253             This program is free software; you can redistribute it and/or modify
254             it under the same terms as Perl itself. The full text of this license
255             can be found in the LICENSE file included with this module.
256              
257             =cut
258              
259             # Local Variables:
260             # mode: cperl
261             # cperl-indent-level: 4
262             # fill-column: 78
263             # indent-tabs-mode: nil
264             # c-indentation-style: bsd
265             # End:
266             # ex: set ts=8 sts=4 sw=4 tw=78 ft=perl expandtab shiftround :