File Coverage

blib/lib/Perl/Critic/Policy/logicLAB/RequireVersionFormat.pm
Criterion Covered Total %
statement 92 94 97.8
branch 30 36 83.3
condition 15 22 68.1
subroutine 22 22 100.0
pod 2 2 100.0
total 161 176 91.4


line stmt bran cond sub pod time code
1             package Perl::Critic::Policy::logicLAB::RequireVersionFormat;
2              
3             # $Id$
4              
5 2     2   1562 use strict;
  2         4  
  2         62  
6 2     2   8 use warnings;
  2         2  
  2         73  
7             use base
8 2     2   10 qw(Perl::Critic::Policy::Modules::RequireVersionVar Perl::Critic::Policy);
  2         2  
  2         1312  
9 2     2   872966 use Perl::Critic::Utils qw{ $SEVERITY_MEDIUM :booleans };
  2         3  
  2         143  
10 2     2   159 use List::MoreUtils qw(any);
  2         5  
  2         27  
11 2     2   745 use Carp qw(carp croak);
  2         3  
  2         143  
12 2     2   44 use 5.008;
  2         4  
13              
14             our $VERSION = '0.08';
15              
16             ## no critic (ValuesAndExpressions::RequireInterpolationOfMetachars)
17             Readonly::Scalar my $EXPL =>
18             q{"$VERSION" variable should conform with the configured};
19             Readonly::Scalar my $DESC => q{"$VERSION" variable not conforming};
20             ## critic [ValuesAndExpressions::RequireInterpolationOfMetachars]
21 2     2   8 use constant supported_parameters => qw(strict_quotes ignore_quotes formats);
  2         3  
  2         162  
22 2     2   9 use constant default_severity => $SEVERITY_MEDIUM;
  2         3  
  2         92  
23 2     2   15 use constant default_themes => qw(logiclab);
  2         2  
  2         96  
24 2     2   9 use constant applies_to => 'PPI::Document';
  2         3  
  2         1479  
25              
26             my @strip_tokens = qw(
27             PPI::Token::Structure
28             PPI::Token::Whitespace
29             );
30              
31             my @parsable_tokens = qw(
32             PPI::Token::Quote::Double
33             PPI::Token::Quote::Single
34             PPI::Token::Number::Float
35             PPI::Token::Number::Version
36             );
37              
38             sub violates {
39 29     29 1 80914 my ( $self, $elem, $doc ) = @_;
40              
41 29         44 my $version_spec = q{};
42 29         36 my $separator;
43              
44 29 100       90 if ( my $stmt = $doc->find_first( \&_is_version_declaration_statement ) ) {
45              
46 28         537 my $tokenizer = PPI::Tokenizer->new( \$stmt );
47 28         2875 my $tokens = $tokenizer->all_tokens;
48              
49 28         12640 ( $version_spec, $separator ) = $self->_extract_version($tokens);
50             }
51              
52 29 50 66     479 if ( $version_spec and $self->{_strict_quotes} and $separator ) {
      33        
53 0 0       0 if ( $separator ne q{'} ) {
54 0         0 return $self->violation( $DESC, $EXPL, $doc );
55             }
56             }
57              
58 29 100 66     486 if ( $version_spec and $self->{_ignore_quotes} and $separator ) {
      66        
59 14         115 $version_spec =~ s/$separator//xsmg;
60             }
61              
62 29         35 my $ok;
63              
64 29         26 foreach my $format ( @{ $self->{_formats} } ) {
  29         67  
65 31 100 100     310 if ( $version_spec and $version_spec =~ m/$format/xsm ) {
66 15         28 $ok++;
67             }
68             }
69              
70 29 100 100     112 if ( $version_spec and not $ok ) {
71 13         55 return $self->violation( $DESC, $EXPL, $doc );
72             }
73              
74 16         40 return;
75             }
76              
77             sub _parse_formats {
78 1     1   1 my ( $self, $config_string ) = @_;
79              
80 1         8 my @formats = split m{ \s* [||] \s* }xms, $config_string;
81              
82 1         2 return \@formats;
83             }
84              
85             sub initialize_if_enabled {
86 2     2 1 3295580 my ( $self, $config ) = @_;
87              
88             #Setting the default
89 2         7 $self->{_formats} = [qw(\A\d+\.\d+(_\d+)?\z)];
90              
91 2   50     9 $self->{_strict_quotes} = $config->get('strict_quotes') || 0;
92 2   50     26 $self->{_ignore_quotes} = $config->get('ignore_quotes') || 1;
93              
94 2         19 my $formats = $config->get('formats');
95              
96 2 100       14 if ($formats) {
97 1         4 $self->{_formats} = $self->_parse_formats($formats);
98             }
99              
100 2         6 return $TRUE;
101             }
102              
103             sub _extract_version {
104 28     28   43 my ( $self, $tokens ) = @_;
105              
106             ##stripping whitespace and structure tokens
107 28         35 my $i = 0;
108 28         28 foreach my $t ( @{$tokens} ) {
  28         50  
109 137 100   244   318 if ( any { ref $t eq $_ } @strip_tokens ) {
  244         383  
110 107         75 splice @{$tokens}, $i, 1;
  107         165  
111             }
112 137         237 $i++;
113             }
114              
115             #Trying to locate and match version containing token
116 28         31 foreach my $t ( @{$tokens} ) {
  28         45  
117 108 100   391   207 if ( any { ref $t eq $_ } @parsable_tokens ) {
  391         427  
118 27 100       55 if ( $t->{separator} ) {
119 14         43 return ( $t->content, $t->{separator} );
120             }
121             else {
122 13         39 return $t->content;
123             }
124             }
125             }
126              
127 1         5 return;
128             }
129              
130             sub _is_version_declaration_statement { ## no critic (ArgUnpacking)
131 48 100   48   657 return 1 if _is_our_version(@_);
132 34 100       72 return 1 if _is_vars_package_version(@_);
133 20         39 return 0;
134             }
135              
136             sub _is_our_version {
137 48     48   71 my ( undef, $elem ) = @_;
138 48 50       156 return if not $elem;
139 48 100       205 $elem->isa('PPI::Statement::Variable') || return 0;
140 14 50       59 $elem->type() eq 'our' || return 0;
141             ## no critic (ValuesAndExpressions::RequireInterpolationOfMetachars)
142 14     14   592 return any { $_ eq '$VERSION' } $elem->variables();
  14         698  
143             }
144              
145             sub _is_vars_package_version {
146 34     34   42 my ( undef, $elem ) = @_;
147 34 50       86 return if not $elem;
148 34 100       110 $elem->isa('PPI::Statement') || return 0;
149             return any {
150 40 100   40   222 $_->isa('PPI::Token::Symbol')
151             and $_->content =~ m{ \A \$(\S+::)*VERSION \z }xms;
152             }
153 17         88 $elem->children();
154             }
155              
156             1;
157              
158             __END__
159              
160             =pod
161              
162             =begin markdown
163              
164             [![CPAN version](https://badge.fury.io/pl/Perl-Critic-Policy-logicLAB-RequireVersionFormat.svg)](http://badge.fury.io/pl/Perl-Critic-Policy-logicLAB-RequireVersionFormat)
165             [![Build Status](https://travis-ci.org/jonasbn/pcpmrvf.svg?branch=master)](https://travis-ci.org/jonasbn/pcpmrvf)
166             [![Coverage Status](https://coveralls.io/repos/jonasbn/pcpmrvf/badge.png)](https://coveralls.io/r/jonasbn/pcpmrvf)
167              
168             =end markdown
169              
170             =head1 NAME
171              
172             Perl::Critic::Policy::logicLAB::RequireVersionFormat - assert version number formats
173              
174             =head1 AFFILIATION
175              
176             This policy is part of L<Perl::Critic::logicLAB> distribution.
177              
178             =head1 VERSION
179              
180             This documentation describes version 0.05
181              
182             =head1 DESCRIPTION
183              
184             This policy asserts that a specified version number conforms to a specified
185             format.
186              
187             The default format is the defacto format used on CPAN. X.X and X.X_X where X
188             is an arbitrary integer, in the code this is expressed using the following
189             regular expression:
190              
191             \A\d+\.\d+(_\d+)?\z
192              
193             The following example lines would adhere to this format:
194              
195             =over
196              
197             =item * 0.01, a regular release
198              
199             =item * 0.01_1, a developer release
200              
201             =back
202              
203             Scope, quoting and representation does not matter. If the version specification
204             is lazy please see L</EXCEPTIONS>.
205              
206             The following example lines would not adhere to this format and would result in
207             a violation.
208              
209             =over
210              
211             =item * our ($VERSION) = '$Revision$' =~ m{ \$Revision: \s+ (\S+) }x;
212              
213             =item * $VERSION = '0.0.1';
214              
215             =item * $MyPackage::VERSION = 1.0.61;
216              
217             =item * use version; our $VERSION = qv(1.0.611);
218              
219             =item * $VERSION = "0.01a";
220              
221             =back
222              
223             In addition to the above examples, there are variations in quoting etc. all
224             would cause a violation.
225              
226             =head2 EXCEPTIONS
227              
228             In addition there are some special cases, were we simply ignore the version,
229             since we cannot assert it in a reasonable manner.
230              
231             =over
232              
233             =item * our $VERSION = $Other::VERSION;
234              
235             We hope that $Other::VERSION conforms where defined, so we ignore for now.
236              
237             =back
238              
239             =head1 CONFIGURATION AND ENVIRONMENT
240              
241             =head2 strict_quotes
242              
243             Strict quotes is off by default.
244              
245             Strict quotes enforces that you version number must be quoted, like so:
246             '0.01' and "0.01". 0.01 would in this case cause a violation. This
247             would also go for any additional formats you could configure as valid using
248             the L</formats> parameter below.
249              
250             [logicLAB::RequireVersionFormat]
251             strict_quotes = 1
252              
253             =head2 ignore_quotes
254              
255             Ignore quotes is on by default.
256              
257             0.01, '0.01' and "0.01" would be interpreted as the same.
258              
259             Disabling ignore quotes, would mean that: '0.01' and "0.01" would violate the
260             default format since quotes are not specifed as part of the pattern. This
261             would also go for any additional formats you could configure as valid using
262             the L</formats> parameter below.
263              
264             [logicLAB::RequireVersionFormat]
265             ignore_quotes = 0
266              
267             =head2 formats
268              
269             If no formats are specified, the policy only enforces the default format
270             mentioned in L</DESCRIPTION> in combination with the above two configuration
271             parameters of course.
272              
273             [logicLAB::RequireVersionFormat]
274             formats = \A\d+\.\d+(_\d+)?\z || \Av\d+\.\d+\.\d+\z
275              
276             =head1 DEPENDENCIES AND REQUIREMENTS
277              
278             =over
279              
280             =item * L<Perl::Critic>
281              
282             =item * L<Perl::Critic::Utils>
283              
284             =item * L<Readonly>
285              
286             =item * L<Test::More>
287              
288             =item * L<Test::Perl::Critic>
289              
290             =back
291              
292             =head1 INCOMPATIBILITIES
293              
294             This distribution has no known incompatibilities.
295              
296             =head1 BUGS AND LIMITATIONS
297              
298             I think it would be a good idea to ignore this particular version string and versions thereof:
299              
300             our ($VERSION) = '$Revision$' =~ m{ \$Revision: \s+ (\S+) }x;
301              
302             I am however still undecided.
303              
304             =head1 BUG REPORTING
305              
306             Please use Requets Tracker for bug reporting:
307              
308             http://rt.cpan.org/NoAuth/Bugs.html?Dist=Perl-Critic-logicLAB-Prohibit-RequireVersionFormat
309              
310             =head1 TEST AND QUALITY
311              
312             The following policies have been disabled for this distribution
313              
314             =over
315              
316             =item * L<Perl::Crititc::Policy::ValuesAndExpressions::ProhibitConstantPragma>
317              
318             =item * L<Perl::Crititc::Policy::NamingConventions::Capitalization>
319              
320             =back
321              
322             =head2 TEST COVERAGE
323              
324             ---------------------------- ------ ------ ------ ------ ------ ------ ------
325             File stmt bran cond sub pod time total
326             ---------------------------- ------ ------ ------ ------ ------ ------ ------
327             ...B/RequireVersionFormat.pm 97.9 75.0 68.2 100.0 100.0 100.0 89.8
328             Total 97.9 75.0 68.2 100.0 100.0 100.0 89.8
329             ---------------------------- ------ ------ ------ ------ ------ ------ ------
330              
331             =head1 TODO
332              
333             =over
334              
335             =item * I would like to integrate the features of this policy into L<Perl::Critic::Policy::Modules::RequireVersionVar>, but I was aiming for a proof of concept first - so this planned patch is still in the pipeline.
336              
337             =item * Address the limitation listed in L</BUGS AND LIMITATIONS>.
338              
339             =back
340              
341             =head1 SEE ALSO
342              
343             =over
344              
345             =item * L<http://logiclab.jira.com/wiki/display/OPEN/Versioning>
346              
347             =item * L<version>
348              
349             =item * L<http://search.cpan.org/dist/Perl-Critic/lib/Perl/Critic/Policy/Modules/RequireVersionVar.pm>
350              
351             =back
352              
353             =head1 AUTHOR
354              
355             =over
356              
357             =item * Jonas B. Nielsen, jonasbn C<< <jonasbn@cpan.org> >>
358              
359             =back
360              
361             =head1 LICENSE AND COPYRIGHT
362              
363             Copyright (c) 2009-2015 Jonas B. Nielsen. All rights reserved.
364              
365             This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
366              
367             =cut