File Coverage

blib/lib/Perl/Critic/Pulp/Utils.pm
Criterion Covered Total %
statement 64 68 94.1
branch 20 28 71.4
condition 21 28 75.0
subroutine 14 15 93.3
pod 4 7 57.1
total 123 146 84.2


line stmt bran cond sub pod time code
1             # Copyright 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017 Kevin Ryde
2              
3             # This file is part of Perl-Critic-Pulp.
4              
5             # Perl-Critic-Pulp is free software; you can redistribute it and/or modify
6             # it under the terms of the GNU General Public License as published by the
7             # Free Software Foundation; either version 3, or (at your option) any later
8             # version.
9             #
10             # Perl-Critic-Pulp is distributed in the hope that it will be useful, but
11             # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
12             # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
13             # for more details.
14             #
15             # You should have received a copy of the GNU General Public License along
16             # with Perl-Critic-Pulp. If not, see <http://www.gnu.org/licenses/>.
17              
18              
19             package Perl::Critic::Pulp::Utils;
20 41     41   1113 use 5.006;
  41         148  
21 41     41   187 use strict;
  41         100  
  41         934  
22 41     41   198 use warnings;
  41         90  
  41         877  
23 41     41   8936 use version (); # but don't import qv()
  41         38458  
  41         1408  
24              
25             our $VERSION = 97;
26              
27 41     41   223 use base 'Exporter';
  41         107  
  41         32353  
28             our @EXPORT_OK = qw(parameter_parse_version
29             version_if_valid
30             include_module_version
31             elem_package
32             elem_in_BEGIN
33             elem_is_comma_operator
34             %COMMA);
35              
36             our %COMMA = (',' => 1,
37             '=>' => 1);
38              
39             sub parameter_parse_version {
40 92     92 1 35502436 my ($self, $parameter, $str) = @_;
41              
42 92         186 my $version;
43 92 50 33     434 if (defined $str && $str ne '') {
44 0         0 $version = version_if_valid ($str);
45 0 0       0 if (! defined $version) {
46 0         0 $self->throw_parameter_value_exception
47             ($parameter->get_name,
48             $str,
49             undef, # source
50             'invalid version number string');
51             }
52             }
53 92         564 $self->__set_parameter_value ($parameter, $version);
54             }
55              
56             # return a version.pm object, or undef if $str is invalid
57             sub version_if_valid {
58 20     20 0 3322 my ($str) = @_;
59             # this is a nasty hack to notice "not a number" warnings, and for version
60             # 0.81 possibly throwing errors too
61 20         28 my $good = 1;
62 20         27 my $version;
63 20     0   26 { local $SIG{'__WARN__'} = sub { $good = 0 };
  20         98  
  0         0  
64 20         28 eval { $version = version->new($str) };
  20         175  
65             }
66 20 50       77 return ($good ? $version : undef);
67             }
68              
69             # This regexp is what Perl's toke.c S_force_version() demands, as of
70             # versions 5.004 through 5.8.9. A version number in a "use" must start with
71             # a digit and then have only digits, dots and underscores. In particular
72             # other normal numeric forms like hex or exponential are not taken to be
73             # version numbers, and even omitting the 0 from a decimal like ".25" is not
74             # a version number.
75             #
76             our $use_module_version_number_re = qr/^v?[0-9][0-9._]*$/;
77              
78             sub include_module_version {
79 416     416 0 174005 my ($inc) = @_;
80              
81             # only a module style "use Foo", not a perl version num like "use 5.010"
82 416 50       769 defined ($inc->module) || return undef;
83              
84 416   100     8127 my $ver = $inc->schild(2) || return undef;
85             # ENHANCE-ME: when PPI recognises v-strings may have to extend this
86 407 100       7105 $ver->isa('PPI::Token::Number') || return undef;
87              
88 162 100       321 $ver->content =~ $use_module_version_number_re or return undef;
89              
90             # must be followed by whitespace, or comment, or end of statement, so
91             #
92             # use Foo 10 -3; <- version 10, arg -3
93             # use Foo 10-3; <- arg 7
94             #
95             # use Foo 10# <- version 10, arg -3
96             # -3;
97             #
98 154 100       1430 if (my $after = $ver->next_sibling) {
99 139 100 66     6630 unless ($after->isa('PPI::Token::Whitespace')
      66        
      100        
100             || $after->isa('PPI::Token::Comment')
101             || ($after->isa('PPI::Token::Structure')
102             && $after eq ';')) {
103 26         74 return undef;
104             }
105             }
106              
107 128         1363 return $ver;
108             }
109              
110             # $inc is a PPI::Statement::Include.
111             # Return the element which is the start of the first argument to its
112             # import() or unimport(), for "use" or "no" respectively.
113             #
114             # A "require" is treated the same as "use" and "no", but arguments to it
115             # like "require Foo::Bar '-init';" is in fact a syntax error.
116             #
117             sub include_module_first_arg {
118 288     288 0 66087 my ($inc) = @_;
119 288 50       619 defined ($inc->module) || return;
120 288         5936 my $arg;
121 288 100       531 if (my $ver = include_module_version ($inc)) {
122 72         198 $arg = $ver->snext_sibling;
123             } else {
124             # eg. "use Foo 'xxx'"
125 216         896 $arg = $inc->schild(2);
126             }
127             # don't return terminating ";"
128 288 50 100     5731 if ($arg
      66        
      66        
129             && $arg->isa('PPI::Token::Structure')
130             && $arg->content eq ';'
131             && ! $arg->snext_sibling) {
132 41         1388 return;
133             }
134 247         1106 return $arg;
135             }
136              
137             # Hack to set Perl::Critic::Violation location to $linenum in $doc_str.
138             # Have thought about validating _location and _source fields before mangling
139             # them, but hopefully there'll be a documented interface to use before long.
140             #
141             sub _violation_override_linenum {
142 193     193   456 my ($violation, $doc_str, $linenum) = @_;
143              
144             # if ($violation->can('set_line_number_offset')) {
145             # $violation->set_line_number_offset ($linenum - 1);
146             # } else {
147              
148 193         693 bless $violation, 'Perl::Critic::Pulp::PodMinimumVersionViolation';
149 193         751 $violation->{_Pulp_linenum_offset} = $linenum - 1;
150 193         433 $violation->{'_source'} = _str_line_n ($doc_str, $linenum);
151              
152 193         543 return $violation;
153             }
154              
155             # starting contents of line number $n within $str
156             # $n==0 is the first line
157             sub _str_line_n {
158 199     199   4145 my ($str, $n) = @_;
159 199         297 $n--;
160 199 50       3140 return ($str =~ /^(.*\n){$n}(.*)/ ? $2 : '');
161             }
162              
163             sub elem_package {
164 96     96 1 152 my ($elem) = @_;
165 96         125 for (;;) {
166 273   100     631 $elem = $elem->sprevious_sibling || $elem->parent
167             || return undef;
168 265 100       5783 if ($elem->isa ('PPI::Statement::Package')) {
169 88         266 return $elem;
170             }
171             }
172             }
173              
174             sub elem_in_BEGIN {
175 12     12 1 481 my ($elem) = @_;
176 12         60 while ($elem = $elem->parent) {
177 29 100       239 if ($elem->isa('PPI::Statement::Scheduled')) {
178 9         158 return ($elem->type eq 'BEGIN');
179             }
180             }
181 3         22 return 0;
182             }
183              
184             sub elem_is_comma_operator {
185 487     487 1 694 my ($elem) = @_;
186             return ($elem->isa('PPI::Token::Operator')
187 487   66     1788 && $Perl::Critic::Pulp::Utils::COMMA{$elem});
188             }
189              
190             1;
191             __END__
192              
193             =for stopwords perlcritic Ryde ie
194              
195             =head1 NAME
196              
197             Perl::Critic::Pulp::Utils - shared helper code for the Pulp perlcritic add-on
198              
199             =head1 SYNOPSIS
200              
201             use Perl::Critic::Pulp::Utils;
202              
203             =head1 DESCRIPTION
204              
205             This is a bit of a grab bag, but works as far as it goes.
206              
207             =head1 FUNCTIONS
208              
209             =head2 Element Functions
210              
211             =over
212              
213             =item C<$pkgelem = Perl::Critic::Pulp::Utils::elem_package ($elem)>
214              
215             C<$elem> is a C<PPI::Element>. Return the C<PPI::Statement::Package>
216             containing C<$elem>, or C<undef> if C<$elem> is not in the scope of any
217             package statement.
218              
219             The search upwards begins with the element preceding C<$elem>, so if
220             C<$elem> itself is a C<PPI::Statement::Package> then that's not the one
221             returned, instead its containing package.
222              
223             =item C<$bool = Perl::Critic::Pulp::Utils::elem_in_BEGIN ($elem)>
224              
225             Return true if C<$elem> (a C<PPI::Element>) is within a C<BEGIN> block
226             (ie. a C<PPI::Statement::Scheduled> of type "BEGIN").
227              
228             =item C<$bool = Perl::Critic::Pulp::Utils::elem_is_comma_operator ($elem)>
229              
230             Return true if C<$elem> (a C<PPI::Element>) is a comma operator
231             (C<PPI::Token::Operator>), either "," or "=>'.
232              
233             =cut
234              
235             # Not sure about this just yet. This first_arg would be a matching pair.
236             #
237             # =item C<$numelem = Perl::Critic::Pulp::Utils::include_module_version ($incelem)>
238             #
239             # C<$incelem> is a C<PPI::Statement::Include>. If it's a module type C<use>
240             # or C<no> with a version number for Perl to check then return that version
241             # number element, otherwise return C<undef>.
242             #
243             # use Foo 1.23 qw(arg1 arg2);
244             # no Bar 0.1;
245             #
246             # A module version is a literal number following the module name, with either
247             # nothing after it for that statement, or with no comma before the statement
248             # arguments.
249             #
250             # C<Exporter> and other module C<import> handlers may interpret a number
251             # argument as a version to be checked, but C<include_module_version> looks
252             # only for version numbers which Perl itself will check.
253             #
254             # A module C<require> type C<$incelem> is treated the same as C<use> and
255             # C<no>, but a module version number like "require Foo::Bar 1.5" is a Perl
256             # syntax error. A Perl version C<$incelem> like C<use 5.004> is not a module
257             # include and the return is C<undef> for it.
258             #
259             # As of PPI 1.203 there's no v-number parsing, so the returned element is only
260             # ever a C<PPI::Token::Number>. Perhaps that will change.
261             #
262             # C<PPI::Statement::Include> has a similar C<$incelem-E<gt>module_version>
263             # method, but it's wrong as of PPI 1.209. It takes all numbers as version
264             # numbers, whereas Perl doesn't accept exponential format floats, only the
265             # restricted number forms of Perl's F<toke.c> C<S_force_version()>.
266              
267             =back
268              
269             =head2 Policy Parameter Functions
270              
271             =over
272              
273             =item C<Perl::Critic::Pulp::Utils::parameter_parse_version ($self, $parameter, $str)>
274              
275             This is designed for use as the C<parser> field of a policy's
276             C<supported_parameters> entry for a parameter which is a version number.
277              
278             { name => 'above_version',
279             description => 'Check only above this version of Perl.',
280             behavior => 'string',
281             parser => \&Perl::Critic::Pulp::Utils::parameter_parse_version,
282             }
283              
284             C<$str> is parsed with the C<version.pm> module. If valid then the
285             parameter is set with C<$self-E<gt>__set_parameter_value> to the resulting
286             C<version> object (so for example field $self->{'_above_version'}). If
287             invalid then an exception is thrown per
288             C<$self-E<gt>throw_parameter_value_exception>.
289              
290             =back
291              
292             =head1 EXPORTS
293              
294             Nothing is exported by default, but the functions can be requested in usual
295             C<Exporter> style,
296              
297             use Perl::Critic::Pulp::Utils 'elem_in_BEGIN';
298             if (elem_in_BEGIN($elem)) {
299             # ...
300             }
301              
302             There's no C<:all> tag since this module is meant as a grab-bag of functions
303             and importing as-yet unknown things would be asking for name clashes.
304              
305             =head1 SEE ALSO
306              
307             L<Perl::Critic::Pulp>,
308             L<Perl::Critic>,
309             L<PPI>
310              
311             =head1 HOME PAGE
312              
313             L<http://user42.tuxfamily.org/perl-critic-pulp/index.html>
314              
315             =head1 COPYRIGHT
316              
317             Copyright 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017 Kevin Ryde
318              
319             Perl-Critic-Pulp is free software; you can redistribute it and/or modify it
320             under the terms of the GNU General Public License as published by the Free
321             Software Foundation; either version 3, or (at your option) any later
322             version.
323              
324             Perl-Critic-Pulp is distributed in the hope that it will be useful, but
325             WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
326             or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
327             more details.
328              
329             You should have received a copy of the GNU General Public License along with
330             Perl-Critic-Pulp. If not, see <http://www.gnu.org/licenses/>.
331              
332             =cut