File Coverage

blib/lib/Perl/Critic/Policy/ValuesAndExpressions/RequireNumericVersion.pm
Criterion Covered Total %
statement 85 93 91.4
branch 31 46 67.3
condition 20 26 76.9
subroutine 16 16 100.0
pod 1 1 100.0
total 153 182 84.0


line stmt bran cond sub pod time code
1             # Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2019 Kevin Ryde
2              
3             # Perl-Critic-Pulp is free software; you can redistribute it and/or modify
4             # it under the terms of the GNU General Public License as published by the
5             # Free Software Foundation; either version 3, or (at your option) any later
6             # version.
7             #
8             # Perl-Critic-Pulp is distributed in the hope that it will be useful, but
9             # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
10             # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
11             # for more details.
12             #
13             # You should have received a copy of the GNU General Public License along
14             # with Perl-Critic-Pulp. If not, see <http://www.gnu.org/licenses/>.
15              
16              
17             package Perl::Critic::Policy::ValuesAndExpressions::RequireNumericVersion;
18 40     40   33502 use 5.006;
  40         163  
19 40     40   280 use strict;
  40         93  
  40         858  
20 40     40   198 use warnings;
  40         144  
  40         1116  
21 40     40   236 use Scalar::Util;
  40         95  
  40         1538  
22 40     40   5620 use version (); # but don't import qv()
  40         22485  
  40         1013  
23              
24 40     40   261 use base 'Perl::Critic::Policy';
  40         139  
  40         5562  
25 40     40   186329 use Perl::Critic::Utils 'precedence_of';
  40         125  
  40         2210  
26 40     40   7573 use Perl::Critic::Pulp::Utils;
  40         184  
  40         1782  
27              
28             # uncomment this to run the ### lines
29             #use Smart::Comments;
30              
31 40     40   320 use constant supported_parameters => ();
  40         171  
  40         3020  
32 40     40   346 use constant default_severity => $Perl::Critic::Utils::SEVERITY_MEDIUM;
  40         1208  
  40         3408  
33 40     40   335 use constant default_themes => qw(pulp bugs);
  40         101  
  40         2254  
34 40     40   296 use constant applies_to => ('PPI::Token::Symbol');
  40         108  
  40         30690  
35              
36             my $perl_510 = version->new('5.10.0');
37             my $assignment_precedence = precedence_of('=');
38              
39             our $VERSION = 98;
40              
41             sub violates {
42 36     36 1 634189 my ($self, $elem, $document) = @_;
43             ### NumericVersion violates()
44              
45             ### canonical: $elem->canonical
46 36   100     85 my $package = _symbol_is_mod_VERSION($elem)
47             || return;
48              
49 32   100     102 my $assign = $elem->snext_sibling || return;
50             ### assign: "$assign"
51 27 50       665 $assign eq '=' or return;
52              
53 27   50     446 my $value = $assign->snext_sibling || return;
54             ### value: "$value"
55              
56 27 100       697 if (! $value->isa('PPI::Token::Quote')) {
57             ### an expression, or a number, not a string, so ok ...
58 8         31 return;
59             }
60 19 50       87 if (_following_expression ($value)) {
61             ### can't check an expression (though it starts with a string) ...
62 0         0 return;
63             }
64              
65 19         342 my $str = $value->string;
66 19 100 100     245 if ($value->isa ('PPI::Token::Quote::Double')
67             || $value->isa ('PPI::Token::Quote::Interpolate')) {
68             ### double quote, check only up to an interpolation
69 16         45 $str =~ s/[\$\@].*//;
70             }
71              
72 19 100       48 if (_any_eval_VERSION ($document, $package)) {
73 3         10 return;
74             }
75              
76 16 100       49 if (! defined(Perl::Critic::Pulp::Utils::version_if_valid($str))) {
77 7         30 return $self->violation
78             ('Non-numeric VERSION string (not recognised by version.pm)',
79             '',
80             $value);
81             }
82              
83             # Float number strings like "1e6" are usually rejected by version.pm, but
84             # have seen perl 5.10 and version.pm 0.88 with pure-perl "version::vpp"
85             # accept them. Not sure why that's so, but explicitly reject to be sure.
86             # Such a string form in fact works in perl 5.8.x but not in 5.10.x.
87             #
88 9 50       44 if ($str =~ /e/i) {
89 0         0 return $self->violation
90             ('Non-numeric VERSION string (exponential string like "1e6" no good in perl 5.10 and up)',
91             '',
92             $value);
93             }
94              
95 9         40 my $got_perl = $document->highest_explicit_perl_version;
96 9 100 100     2599 if (defined $got_perl && $got_perl >= $perl_510) {
97             # for 5.10 up only need to satisfy version.pm
98 2         9 return;
99             }
100              
101             # for 5.8 or unspecified version must be plain number, not "1.2.3" etc
102 7 50       35 if (! Scalar::Util::looks_like_number($str)) {
103 7         26 return $self->violation ('Non-numeric VERSION string',
104             '',
105             $value);
106             }
107 0         0 return;
108             }
109              
110             sub _following_expression {
111 19     19   46 my ($elem) = @_;
112 19 100       67 my $after = $elem->snext_sibling
113             or return 0;
114              
115 6 50       140 if ($after->isa('PPI::Token::Structure')) {
    0          
116 6         20 return 0;
117             } elsif ($after->isa('PPI::Token::Operator')) {
118 0 0       0 if (precedence_of($after) >= $assignment_precedence) {
119 0         0 return 0;
120             }
121 0 0       0 if ($after eq '.') {
122 0         0 return 0;
123             }
124             }
125 0         0 return 1;
126             }
127              
128             # $elem is a PPI::Token::Word
129             # return its module, such as "Foo::Bar"
130             # or if it's in "main" then return undef
131             #
132             sub _symbol_is_mod_VERSION {
133 66     66   140 my ($elem) = @_;
134              
135             # canonical() turns $::VERSION into $main::VERSION
136 66 50       213 $elem->canonical =~ /^\$((\w+::)*)VERSION$/
137             or return undef; # not $VERSION or $Foo::VERSION
138 66         1213 my $package = substr($1,0,-2);
139              
140 66 100       177 if ($package eq '') {
141             # $elem is an unqualified symbol, find containing "package Foo"
142 54   100     191 my $pelem = Perl::Critic::Pulp::Utils::elem_package($elem)
143             || return undef; # not in a package, not a module $VERSION
144 53         175 $package = $pelem->namespace;
145             }
146              
147 65 100       1398 if ($package eq 'main') {
148 3         13 return undef; # "package main" or "$main::VERSION", not a module
149             }
150 62         195 return $package;
151             }
152              
153             # return true if there's a "$VERSION = eval $VERSION" somewhere in
154             # $document, acting on the "$VERSION" of $want_package
155             #
156             sub _any_eval_VERSION {
157 19     19   47 my ($document, $want_package) = @_;
158              
159 19   50     63 my $aref = $document->find('PPI::Token::Symbol') || return 0;
160 19         247 foreach my $elem (@$aref) {
161 27   50     57 my $got_package = _symbol_is_mod_VERSION($elem) || next;
162 27 100       77 $got_package eq $want_package || next;
163              
164 25   100     67 my $assign = $elem->snext_sibling || next;
165 24 50       547 $assign eq '=' or next;
166              
167 24   50     356 my $value = $assign->snext_sibling || next;
168 24 100       637 $value->isa('PPI::Token::Word') || next;
169 4 50       12 $value eq 'eval' or next;
170              
171 4   50     61 $value = $value->snext_sibling || next;
172 4 100       106 $value->isa('PPI::Token::Symbol') || next;
173 3   50     9 $got_package = _symbol_is_mod_VERSION($value) || next;
174 3 50       10 $got_package eq $want_package || next;
175              
176 3         11 return 1;
177             }
178 16         67 return 0;
179             }
180              
181             1;
182             __END__
183              
184             =for stopwords toplevel ie CPAN pre-release args exponentials multi-dots v-nums YYYYMMDD Ryde builtin MakeMaker runtime filename
185              
186             =head1 NAME
187              
188             Perl::Critic::Policy::ValuesAndExpressions::RequireNumericVersion - $VERSION a plain number
189              
190             =head1 DESCRIPTION
191              
192             This policy is part of the L<C<Perl::Critic::Pulp>|Perl::Critic::Pulp>
193             add-on. It asks you to use a plain number in a module C<$VERSION> so that
194             Perl's builtin version works.
195              
196             Any literal number is fine, or a string which is a number,
197              
198             $VERSION = 123; # ok
199             $VERSION = '1.5'; # ok
200             $VERSION = 1.200_001; # ok
201              
202             For Perl 5.10 and higher the extra forms of the C<version> module too,
203              
204             use 5.010;
205             $VERSION = '1.200_001'; # ok for 5.10 up, version.pm
206              
207             But a non-number string is not allowed,
208              
209             $VERSION = '1.2alpha'; # bad
210              
211             The idea of this requirement is that a plain number is needed for Perl's
212             builtin module version checking like the following, and on that basis this
213             policy is under the "bugs" theme (see L<Perl::Critic/POLICY THEMES>).
214              
215             use Foo 1.0;
216             Foo->VERSION(1);
217              
218             A plain number is also highly desirable so applications can do their own
219             compares like
220              
221             if (Foo->VERSION >= 1.234) {
222              
223             In each case if C<$VERSION> is not a number then it provokes warnings, and
224             may end up appearing as a lesser version than intended.
225              
226             Argument "1.2.alpha" isn't numeric in subroutine entry
227              
228             If you've loaded the C<version.pm> module then a C<$VERSION> not accepted by
229             C<version.pm> will in fact croak, which is an unpleasant variant behaviour.
230              
231             use version ();
232             print "version ",Foo->VERSION,"\n";
233             # croaks "Invalid version format ..." if $Foo::VERSION is bad
234              
235             =head2 Scripts
236              
237             This policy only looks at C<$VERSION> in modules. C<$VERSION> in a script
238             can be anything since it won't normally be part of C<use> checks etc.
239             A script C<$VERSION> is anything outside any C<package> statement scope, or
240             under an explicit C<package main>.
241              
242             package main;
243             $VERSION = '1.5.prerelease'; # ok, script
244              
245             $main::VERSION = 'blah'; # ok, script
246             $::VERSION = 'xyzzy'; # ok, script
247              
248             A fully-qualified package name is recognised as belonging to a module,
249              
250             $Foo::Bar::VERSION = 'xyzzy'; # bad
251              
252             =head2 Underscores in Perl 5.8 and Earlier
253              
254             In Perl 5.8 and earlier a string like "1.200_333" is truncated to the
255             numeric part, ie. 1.200, and can thus fail to satisfy
256              
257             $VERSION = '1.222_333'; # bad
258             use Foo 1.222_331; # not satisfied by $VERSION='string' form
259              
260             But an actual number literal with an "_" is allowed. Underscores in
261             literals are stripped out (see L<perldata>), but not in the automatic string
262             to number conversion so a string like C<$VERSION = '1.222_333'> provokes a
263             warning and stops at 1.222.
264              
265             $VERSION = 1.222_333; # ok
266              
267             On CPAN an underscore in a distribution version number is rated as a
268             developer pre-release. But don't put it in module C<$VERSION> strings due
269             to the problems above. The suggestion is to include the underscore in the
270             distribution filename but either omit it from the C<$VERSION> or make it a
271             number literal not a string,
272              
273             $VERSION = 1.002003; # ok
274             $VERSION = 1.002_003; # ok, but not for VERSION_FROM
275              
276             C<ExtUtils::MakeMaker> C<VERSION_FROM> will take the latter as its numeric
277             value, ie. "1.002003" not "1.002_003" as the distribution version. For the
278             latter you can either put an explicit C<VERSION> in F<Makefile.PL>
279              
280             use ExtUtils::MakeMaker;
281             WriteMakefile (VERSION => '1.002_003');
282              
283             Or you can trick MakeMaker with a string plus C<eval>,
284              
285             $VERSION = '1.002_003'; # ok evalled down
286             $VERSION = eval $VERSION;
287              
288             C<MakeMaker> sees the string "1.002_003" but at runtime the C<eval> crunches
289             it down to a plain number 1.002003. C<RequireNumericVersion> notices such
290             an C<eval> and anything in C<$VERSION>. Something bizarre in C<$VERSION>
291             won't be noticed, but that's too unlikely to worry about.
292              
293             =head2 C<version> module in Perl 5.10 up
294              
295             In Perl 5.10 C<use> etc module version checks parse C<$VERSION> with the
296             C<version.pm> module. This policy allows the C<version> module forms if
297             there's an explicit C<use 5.010> or higher in the file.
298              
299             use 5.010;
300             $VERSION = '1.222_333'; # ok for 5.10
301             $VERSION = '1.2.3'; # ok for 5.10
302              
303             But this is still undesirable, as an application check like
304              
305             if (Foo->VERSION >= 1.234) {
306              
307             gets the raw string from C<$VERSION> and thus a non-numeric warning and
308             truncation. Perhaps applications should let C<UNIVERSAL.pm> do the work
309             with say
310              
311             if (eval { Foo->VERSION(1.234) }) {
312              
313             or apply C<version-E<gt>new()> to one of the args. Maybe another policy to
314             not explicitly compare C<$VERSION>, or perhaps an option to tighten this
315             policy to require numbers even in 5.10?
316              
317             =head2 Exponential Format
318              
319             Exponential strings like "1e6" are disallowed
320              
321             $VERSION = '2.125e6'; # bad
322              
323             Except with the C<eval> trick as per above
324              
325             $VERSION = '2.125e6'; # ok
326             $VERSION = eval $VERSION;
327              
328             Exponential number literals are fine.
329              
330             $VERSION = 1e6; # ok
331              
332             Exponential strings don't work in Perl 5.10 because they're not recognised
333             by the C<version> module (v0.82). They're fine in Perl 5.8 and earlier, but
334             in the interests of maximum compatibility this policy treats such a string
335             as non-numeric. Exponentials in versions should be unusual anyway.
336              
337             =head2 Disabling
338              
339             If you don't care about this policy at all then you can disable from your
340             F<.perlcriticrc> in the usual way (see L<Perl::Critic/CONFIGURATION>),
341              
342             [-ValuesAndExpressions::RequireNumericVersion]
343              
344             =head2 Other Ways to Do It
345              
346             The version number system with underscores, multi-dots, v-nums, etc is
347             diabolical mess, and each new addition to it just seems to make it worse.
348             Even the original floating point in version checks is asking for rounding
349             error trouble, though normally fine in practice. A radical simplification
350             is to just use integer version numbers.
351              
352             $VERSION = 123;
353              
354             If you want sub-versions then increment by 100 or some such. Even a
355             YYYYMMDD date is a possibility.
356              
357             $VERSION = 20110328;
358              
359             =head1 SEE ALSO
360              
361             L<Perl::Critic::Pulp>,
362             L<Perl::Critic>
363              
364             L<Perl::Critic::Policy::Modules::RequireVersionVar>,
365             L<Perl::Critic::Policy::ValuesAndExpressions::ProhibitComplexVersion>,
366             L<Perl::Critic::Policy::ValuesAndExpressions::RequireConstantVersion>
367              
368             L<Perl::Critic::Policy::ValuesAndExpressions::ProhibitVersionStrings>,
369             L<Perl::Critic::Policy::Modules::ProhibitUseQuotedVersion>
370              
371             =head1 HOME PAGE
372              
373             http://user42.tuxfamily.org/perl-critic-pulp/index.html
374              
375             =head1 COPYRIGHT
376              
377             Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2019 Kevin Ryde
378              
379             Perl-Critic-Pulp is free software; you can redistribute it and/or modify it
380             under the terms of the GNU General Public License as published by the Free
381             Software Foundation; either version 3, or (at your option) any later
382             version.
383              
384             Perl-Critic-Pulp is distributed in the hope that it will be useful, but
385             WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
386             or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
387             more details.
388              
389             You should have received a copy of the GNU General Public License along with
390             Perl-Critic-Pulp. If not, see <http://www.gnu.org/licenses/>.
391              
392             =cut
393