package Perl::Critic::Policy::Subroutines::ProtectPrivateSubs; use 5.010001; use strict; use warnings; use English qw< $EVAL_ERROR -no_match_vars >; use Readonly; use Perl::Critic::Utils qw< :severities $EMPTY is_function_call is_method_call hashify >; use parent 'Perl::Critic::Policy'; our $VERSION = '1.152'; #----------------------------------------------------------------------------- Readonly::Scalar my $DESC => q; Readonly::Scalar my $EXPL => q; #----------------------------------------------------------------------------- sub supported_parameters { return ( { name => 'private_name_regex', description => 'Pattern that determines what a private subroutine is.', default_string => '\b_\w+\b', ## no critic (RequireInterpolationOfMetachars) behavior => 'string', parser => \&_parse_private_name_regex, }, { name => 'allow', description => q, default_string => $EMPTY, behavior => 'string list', list_always_present_values => [ qw< POSIX::_PC_CHOWN_RESTRICTED POSIX::_PC_LINK_MAX POSIX::_PC_MAX_CANON POSIX::_PC_MAX_INPUT POSIX::_PC_NAME_MAX POSIX::_PC_NO_TRUNC POSIX::_PC_PATH_MAX POSIX::_PC_PIPE_BUF POSIX::_PC_VDISABLE POSIX::_POSIX_ARG_MAX POSIX::_POSIX_CHILD_MAX POSIX::_POSIX_CHOWN_RESTRICTED POSIX::_POSIX_JOB_CONTROL POSIX::_POSIX_LINK_MAX POSIX::_POSIX_MAX_CANON POSIX::_POSIX_MAX_INPUT POSIX::_POSIX_NAME_MAX POSIX::_POSIX_NGROUPS_MAX POSIX::_POSIX_NO_TRUNC POSIX::_POSIX_OPEN_MAX POSIX::_POSIX_PATH_MAX POSIX::_POSIX_PIPE_BUF POSIX::_POSIX_SAVED_IDS POSIX::_POSIX_SSIZE_MAX POSIX::_POSIX_STREAM_MAX POSIX::_POSIX_TZNAME_MAX POSIX::_POSIX_VDISABLE POSIX::_POSIX_VERSION POSIX::_SC_ARG_MAX POSIX::_SC_CHILD_MAX POSIX::_SC_CLK_TCK POSIX::_SC_JOB_CONTROL POSIX::_SC_NGROUPS_MAX POSIX::_SC_OPEN_MAX POSIX::_SC_PAGESIZE POSIX::_SC_SAVED_IDS POSIX::_SC_STREAM_MAX POSIX::_SC_TZNAME_MAX POSIX::_SC_VERSION POSIX::_exit > ], }, ); } sub default_severity { return $SEVERITY_MEDIUM } sub default_themes { return qw( core maintenance certrule ) } sub applies_to { return 'PPI::Token::Word' } #----------------------------------------------------------------------------- sub _parse_private_name_regex { my ($self, $parameter, $config_string) = @_; $config_string //= $parameter->get_default_string(); my $regex; eval { $regex = qr/$config_string/; 1 } ## no critic (RegularExpressions) or $self->throw_parameter_value_exception( 'private_name_regex', $config_string, undef, "is not a valid regular expression: $EVAL_ERROR", ); $self->__set_parameter_value($parameter, $regex); return; } #----------------------------------------------------------------------------- sub violates { my ( $self, $elem, undef ) = @_; if ( my $prior = $elem->sprevious_sibling() ) { state $exceptions = { hashify( qw( package require use ) ) }; return if exists $exceptions->{ $prior->content() }; } if ( $self->_is_other_pkg_private_function($elem) or $self->_is_other_pkg_private_method($elem) ) { return $self->violation( $DESC, $EXPL, $elem ); } return; # ok! } sub _is_other_pkg_private_function { my ( $self, $elem ) = @_; return if ! is_method_call($elem) && ! is_function_call($elem); my $private_name_regex = $self->{_private_name_regex}; my $content = $elem->content(); return $content =~ m< \w+::$private_name_regex \z >xms && $content !~ m< \A SUPER::$private_name_regex \z >xms && ! $self->{_allow}{$content}; } sub _is_other_pkg_private_method { my ( $self, $elem ) = @_; my $private_name_regex = $self->{_private_name_regex}; my $content = $elem->content(); # look for structures like "Some::Package->_foo()" return if $content !~ m< \A $private_name_regex \z >xms; my $operator = $elem->sprevious_sibling() or return; return if $operator->content() ne q[->]; my $package = $operator->sprevious_sibling() or return; return if not $package->isa('PPI::Token::Word'); # sometimes the previous sib is a keyword, as in: # shift->_private_method(); This is typically used as # shorthand for "my $self=shift; $self->_private_method()" return if $package->content() eq 'shift' or $package->content() eq '__PACKAGE__'; # Maybe the user wanted to exempt this explicitly. return if $self->{_allow}{"${package}::$content"}; return 1; } 1; __END__ #----------------------------------------------------------------------------- =pod =head1 NAME Perl::Critic::Policy::Subroutines::ProtectPrivateSubs - Prevent access to private subs in other packages. =head1 AFFILIATION This Policy is part of the core L distribution. =head1 DESCRIPTION By convention Perl authors (like authors in many other languages) indicate private methods and variables by inserting a leading underscore before the identifier. This policy catches attempts to access private variables from outside the package itself. The subroutines in the L package which begin with an underscore (e.g. C) are not flagged as errors by this policy. =head1 CONFIGURATION You can define what a private subroutine name looks like by specifying a regular expression for the C option in your F<.perlcriticrc>: [Subroutines::ProtectPrivateSubs] private_name_regex = _(?!_)\w+ The above example is a way of saying that subroutines that start with a double underscore are not considered to be private. (Perl::Critic, in its implementation, uses leading double underscores to indicate a distribution-private subroutine-- one that is allowed to be invoked by other Perl::Critic modules, but not by anything outside of Perl::Critic.) You can configure additional subroutines to accept by specifying them in a space-delimited list to the C option: [Subroutines::ProtectPrivateSubs] allow = FOO::_bar FOO::_baz These are added to the default list of exemptions from this policy. Allowing a subroutine also allows the corresponding method call. So C<< FOO::_bar >> in the above example allows both C<< FOO::_bar() >> and C<< FOO->_bar() >>. =head1 HISTORY This policy is inspired by a similar test in L. =head1 BUGS Doesn't forbid C<< $pkg->_foo() >> because it can't tell the difference between that and C<< $self->_foo() >>. =head1 SEE ALSO L =head1 AUTHOR Chris Dolan =head1 COPYRIGHT Copyright (c) 2006-2011 Chris Dolan. This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself. The full text of this license can be found in the LICENSE file included with this module. =cut # Local Variables: # mode: cperl # cperl-indent-level: 4 # fill-column: 78 # indent-tabs-mode: nil # c-indentation-style: bsd # End: # ex: set ts=8 sts=4 sw=4 tw=78 ft=perl expandtab shiftround :