File Coverage

blib/lib/Perl/Critic/Policy/Subroutines/ProhibitCallsToUnexportedSubs.pm
Criterion Covered Total %
statement 88 96 91.6
branch 16 26 61.5
condition 1 2 50.0
subroutine 19 21 90.4
pod 5 6 83.3
total 129 151 85.4


line stmt bran cond sub pod time code
1             package Perl::Critic::Policy::Subroutines::ProhibitCallsToUnexportedSubs;
2              
3 1     1   1078 use strict;
  1         3  
  1         43  
4 1     1   6 use warnings;
  1         2  
  1         60  
5 1     1   7 use base 'Perl::Critic::Policy';
  1         2  
  1         200  
6              
7 1     1   590 use PPI::Document;
  1         132136  
  1         51  
8 1     1   773 use File::PathList;
  1         940  
  1         69  
9              
10 1         134 use Perl::Critic::Utils qw(
11             :characters
12             :severities
13             &hashify
14             &is_function_call
15             &is_perl_builtin
16             &is_qualified_name
17             &policy_short_name
18 1     1   9 );
  1         3  
19              
20 1         1642 use Perl::Critic::StricterSubs::Utils qw{
21             &find_exported_subroutine_names
22             &find_subroutine_calls
23 1     1   513 };
  1         3  
24              
25             #-----------------------------------------------------------------------------
26              
27             our $VERSION = '0.08';
28              
29             #-----------------------------------------------------------------------------
30              
31             my $CONFIG_PATH_SPLIT_REGEX = qr/ \s* [|] \s* /xms;
32              
33             #-----------------------------------------------------------------------------
34              
35             sub supported_parameters {
36 0     0 0 0 return qw( at_inc_prefix use_standard_at_inc at_inc_suffix );
37             }
38              
39 18     18 1 214 sub default_severity { return $SEVERITY_HIGH }
40 0     0 1 0 sub default_themes { return qw( strictersubs bugs ) }
41 5     5 1 102650 sub applies_to { return 'PPI::Document' }
42              
43             #-----------------------------------------------------------------------------
44              
45             sub new {
46 5     5 1 31509 my ( $class, %config ) = @_;
47 5         18 my $self = bless {}, $class;
48              
49 5         22 my @at_inc_prefix;
50             my @at_inc_suffix;
51              
52 5 50       24 if ( defined $config{at_inc_prefix} ) {
53             @at_inc_prefix =
54 5         46 split $CONFIG_PATH_SPLIT_REGEX, $config{at_inc_prefix};
55             }
56 5 50       22 if ( defined $config{at_inc_suffix} ) {
57             @at_inc_prefix =
58 0         0 split $CONFIG_PATH_SPLIT_REGEX, $config{at_inc_suffix};
59             }
60              
61 5   50     34 my $use_standard_at_inc = $config{use_standard_at_inc} // 1;
62              
63 5         18 my @inc = @at_inc_prefix;
64 5 50       18 if ($use_standard_at_inc) {
65 5         39 push @inc, @INC;
66             }
67 5         12 push @inc, @at_inc_suffix;
68              
69 5 50       16 die policy_short_name(__PACKAGE__), " has no directories in its module search path.\n"
70             if not @inc;
71              
72              
73 5         72 $self->{_inc} = File::PathList->new( paths => \@inc, cache => 1 );
74 5         324 $self->{_exports_by_package} = {};
75 5         26 return $self;
76             }
77              
78             #-----------------------------------------------------------------------------
79              
80             sub _get_inc {
81 4     4   11 my $self = shift;
82 4         10 return $self->{_inc};
83             }
84              
85             sub _get_exports_by_package {
86 28     28   86 my $self = shift;
87 28         83 return $self->{_exports_by_package};
88             }
89              
90             #-----------------------------------------------------------------------------
91              
92             sub violates {
93 5     5 1 87 my ($self, undef, $doc) = @_;
94              
95 5         14 my @violations;
96 5         13 my $expl = q{Violates encapsulation};
97              
98 5         38 for my $sub_call ( find_subroutine_calls($doc) ) {
99 29 50       2809 next if not is_qualified_name( $sub_call );
100              
101 29         306 my ($package, $sub_name) = _parse_subroutine_call( $sub_call );
102 29 100       80 next if _is_builtin_package( $package );
103              
104 24         65 my $exports = $self->_get_exports_for_package( $package );
105 24 100       61 if ( not exists $exports->{ $sub_name } ){
106              
107 18         42 my $desc = qq{Subroutine "$sub_name" not exported by "$package"};
108 18         80 push @violations, $self->violation( $desc, $expl, $sub_call );
109             }
110              
111             }
112              
113 5         472 return @violations;
114             }
115              
116             #-----------------------------------------------------------------------------
117              
118             sub _parse_subroutine_call {
119 29     29   65 my ($sub_call) = @_;
120 29 50       100 return if not $sub_call;
121              
122 29         61 my $sub_name = $EMPTY;
123 29         176 my $package_name = $EMPTY;
124              
125 29 50       59 if ($sub_call =~ m/ \A &? (.*) :: ([^:]+) \z /xms) {
126 29         257 $package_name = $1;
127 29         72 $sub_name = $2;
128             }
129              
130 29         86 return ($package_name, $sub_name);
131             }
132              
133              
134             #-----------------------------------------------------------------------------
135              
136             sub _get_exports_for_package {
137 24     24   48 my ( $self, $package_name ) = @_;
138              
139 24         75 my $exports = $self->_get_exports_by_package()->{$package_name};
140 24 100       71 if (not $exports) {
141 4         10 $exports = {};
142              
143 4         17 my $file_name =
144             $self->_get_file_name_for_package_name( $package_name );
145              
146 4 50       17 if ($file_name) {
147 4         19 $exports =
148             { hashify( _get_exports_from_file( $file_name ) ) };
149             }
150              
151 4         1949 $self->_get_exports_by_package()->{$package_name} = $exports;
152             }
153              
154 24         47 return $exports;
155             }
156              
157             #-----------------------------------------------------------------------------
158              
159             sub _get_exports_from_file {
160 4     4   12 my ($file_name) = @_;
161              
162 4         53 my $doc = PPI::Document->new($file_name);
163 4 50       48353 if (not $doc) {
164 0         0 my $pname = policy_short_name(__PACKAGE__);
165 0         0 die "$pname: could not parse $file_name: $PPI::Document::errstr\n";
166             }
167              
168 4         38 return find_exported_subroutine_names( $doc );
169             }
170              
171             #-----------------------------------------------------------------------------
172              
173             sub _get_file_name_for_package_name {
174 4     4   12 my ($self, $package_name) = @_;
175              
176 4         9 my $partial_path = $package_name;
177 4         12 $partial_path =~ s{::}{/}xmsg;
178 4         11 $partial_path .= '.pm';
179              
180 4         20 my $full_path = $self->_find_file_in_at_INC( $partial_path );
181 4         12 return $full_path;
182             }
183              
184             #-----------------------------------------------------------------------------
185              
186             sub _find_file_in_at_INC { ## no critic (NamingConventions::Capitalization)
187 4     4   12 my ($self, $partial_path) = @_;
188              
189 4         20 my $inc = $self->_get_inc();
190 4         50 my $full_path = $inc->find_file( $partial_path );
191              
192 4 50       720 if (not $full_path) {
193             #TODO reinstate Elliot's error message here.
194 0         0 my $policy_name = policy_short_name( __PACKAGE__ );
195 0         0 warn qq{$policy_name: Cannot find source file "$partial_path"\n};
196 0         0 return;
197             }
198              
199 4         13 return $full_path;
200             }
201              
202             #-----------------------------------------------------------------------------
203              
204             my %BUILTIN_PACKAGES = hashify( qw(CORE CORE::GLOBAL UNIVERSAL main), $EMPTY );
205              
206             sub _is_builtin_package {
207 29     29   56 my ($package_name) = @_;
208 29         86 return exists $BUILTIN_PACKAGES{$package_name};
209             }
210              
211             #-----------------------------------------------------------------------------
212              
213             1;
214              
215             __END__
216              
217             =pod
218              
219             =for stopwords callee's
220              
221             =head1 NAME
222              
223             Perl::Critic::Policy::Subroutines::ProhibitCallsToUnexportedSubs
224              
225             =head1 AFFILIATION
226              
227             This policy is part of L<Perl::Critic::StricterSubs|Perl::Critic::StricterSubs>.
228              
229             =head1 DESCRIPTION
230              
231             Many Perl modules define their public interface by exporting subroutines via
232             L<Exporter|Exporter>. The goal of this Policy is to enforce encapsulation by by
233             prohibiting calls to subroutines that are not listed in the callee's C<@EXPORT>
234             or C<@EXPORT_OK>.
235              
236             =head1 LIMITATIONS
237              
238             This Policy does not properly deal with the L<only|only> pragma or modules that
239             don't use L<Exporter|Exporter> for their export mechanism, such as L<CGI|CGI>. In the
240             near future, we might fix this by allowing you configure the policy with
241             a list of packages that are exempt from this policy.
242              
243             =head1 DIAGNOSTICS
244              
245             =over
246              
247             =item C<Subroutines::ProhibitCallsToUnexportedSubs: Cannot find source file>
248              
249             This warning usually indicates that the file under analysis includes modules
250             that are not installed in this perl's <@INC> paths. See L</"CONFIGURATION">
251             for controlling the C<@INC> list this Policy.
252              
253             This warning can also happen when one of the included modules contains
254             multiple packages, or if the package name doesn't match the file name.
255             L<Perl::Critic|Perl::Critic> advises against both of these conditions, and has additional
256             Policies to help enforce that.
257              
258             =back
259              
260             =head1 SEE ALSO
261              
262             L<Perl::Critic::Policy::Modules::ProhibitMultiplePackages|Perl::Critic::Policy::Modules::ProhibitMultiplePackages>
263              
264             L<Perl::Critic::Policy::Modules::RequireFilenameMatchesPackage|Perl::Critic::Policy::Modules::RequireFilenameMatchesPackage>
265              
266             =head1 AUTHOR
267              
268             Jeffrey Ryan Thalhammer <thaljef@cpan.org>
269              
270             =head1 COPYRIGHT
271              
272             Copyright 2007-2024 Jeffrey Ryan Thalhammer and Andy Lester
273              
274             This program is free software; you can redistribute it and/or modify it under
275             the same terms as Perl itself. The full text of this license can be found in
276             the LICENSE file included with this module.
277              
278             =cut
279              
280              
281             ##############################################################################
282             # Local Variables:
283             # mode: cperl
284             # cperl-indent-level: 4
285             # fill-column: 78
286             # indent-tabs-mode: nil
287             # c-indentation-style: bsd
288             # End:
289             # ex: set ts=8 sts=4 sw=4 tw=78 ft=perl expandtab :