File Coverage

blib/lib/Perl/Critic/Policy/logicLAB/RequireVersionFormat.pm
Criterion Covered Total %
statement 93 95 97.8
branch 30 36 83.3
condition 16 22 72.7
subroutine 22 22 100.0
pod 2 2 100.0
total 163 177 92.0


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