File Coverage

blib/lib/Perl/MinimumVersion/Fast.pm
Criterion Covered Total %
statement 136 138 98.5
branch 88 100 88.0
condition 82 95 86.3
subroutine 14 14 100.0
pod 5 5 100.0
total 325 352 92.3


line stmt bran cond sub pod time code
1             package Perl::MinimumVersion::Fast;
2 6     6   348340 use 5.008005;
  6         67  
3 6     6   33 use strict;
  6         12  
  6         124  
4 6     6   30 use warnings;
  6         10  
  6         152  
5              
6 6     6   2820 use version ();
  6         11766  
  6         201  
7              
8 6     6   2843 use Compiler::Lexer 0.13;
  6         38726  
  6         349  
9 6     6   47 use List::Util qw(max);
  6         13  
  6         11391  
10              
11             our $VERSION = "0.19";
12              
13             my $MIN_VERSION = version->new('5.006');
14             my $VERSION_5_020 = version->new('5.020');
15             my $VERSION_5_018 = version->new('5.018');
16             my $VERSION_5_016 = version->new('5.016');
17             my $VERSION_5_014 = version->new('5.014');
18             my $VERSION_5_012 = version->new('5.012');
19             my $VERSION_5_010 = version->new('5.010');
20             my $VERSION_5_008 = version->new('5.008');
21              
22             sub new {
23 115     115 1 31192 my ($class, $stuff) = @_;
24              
25 115         217 my $filename;
26             my $src;
27 115 100       326 if (ref $stuff ne 'SCALAR') {
28 1         3 $filename = $stuff;
29 1 50       47 open my $fh, '<', $filename
30             or die "Unknown file: $filename";
31 1         5 $src = do { local $/; <$fh> };
  1         5  
  1         33  
32             } else {
33 114         179 $filename = '-';
34 114         197 $src = $$stuff;
35             }
36              
37 115         397 my $lexer = Compiler::Lexer->new($filename);
38 115         11471 my @tokens = $lexer->tokenize($src);
39              
40 115         447 my $self = bless { }, $class;
41 115         331 $self->{minimum_explicit_version} = $self->_build_minimum_explicit_version(\@tokens);
42 115         327 $self->{minimum_syntax_version} = $self->_build_minimum_syntax_version(\@tokens);
43 115         1064 $self;
44             }
45              
46             sub _build_minimum_explicit_version {
47 115     115   277 my ($self, $tokens) = @_;
48 115         167 my @tokens = map { @$_ } @{$tokens};
  115         344  
  115         259  
49              
50 115         206 my $explicit_version;
51 115         341 for my $i (0..@tokens-1) {
52 885 100 100     2697 if ($tokens[$i]->{name} eq 'UseDecl' || $tokens[$i]->{name} eq 'RequireDecl') {
53 30 50       84 if (@tokens >= $i+1) {
54 30         48 my $next_token = $tokens[$i+1];
55 30 100 100     134 if ($next_token->{name} eq 'Double' or $next_token->{name} eq 'VersionString') {
56 7   50     142 $explicit_version = max($explicit_version || 0, version->new($next_token->{data}));
57             }
58             }
59             }
60             }
61 115         353 return $explicit_version;
62             }
63              
64             sub _build_minimum_syntax_version {
65 115     115   214 my ($self, $tokens) = @_;
66 115         172 my @tokens = map { @$_ } @{$tokens};
  115         268  
  115         215  
67 115         203 my $syntax_version = $MIN_VERSION;
68              
69             my $test = sub {
70 79     79   206 my ($reason, $version) = @_;
71 79         508 $syntax_version = max($syntax_version, $version);
72 79         136 push @{$self->{version_markers}->{$version}}, $reason;
  79         498  
73 115         509 };
74              
75 115         264 for my $i (0..@tokens-1) {
76 885         2136 my $token = $tokens[$i];
77 885 100 100     6479 if ($token->{name} eq 'ToDo') {
    100 33        
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    50          
78             # ... => 5.12
79 3         8 $test->('yada-yada-yada operator(...)' => $VERSION_5_012);
80             } elsif ($token->{name} eq 'Package') {
81 12 100 100     63 if (@tokens > $i+2 && $tokens[$i+1]->name eq 'Class') {
82 10         86 my $number = $tokens[$i+2];
83 10 100 100     54 if ($number->{name} eq 'Int' || $number->{name} eq 'Double' || $number->{name} eq 'VersionString') {
    100 100        
84             # package NAME VERSION; => 5.012
85 7         18 $test->('package NAME VERSION' => $VERSION_5_012);
86              
87 7 100 66     29 if (@tokens > $i+3 && $tokens[$i+3]->{name} eq 'LeftBrace') {
88 3         8 $test->('package NAME VERSION BLOCK' => $VERSION_5_014);
89             }
90             } elsif ($tokens[$i+2]->{name} eq 'LeftBrace') {
91 1         6 $test->('package NAME BLOCK' => $VERSION_5_014);
92             }
93             }
94             } elsif ($token->{name} eq 'UseDecl' || $token->{name} eq 'RequireDecl') {
95 30 50       67 if (@tokens >= $i+1) {
96             # use feature => 5.010
97 30         109 my $next_token = $tokens[$i+1];
98 30 100       106 if ($next_token->{data} eq 'feature') {
    100          
99 11 100       26 if (@tokens > $i+2) {
100 9         14 my $next_token = $tokens[$i+2];
101 9 100       26 if ($next_token->name eq 'String') {
102 8         57 my $arg = $next_token->data;
103 8         44 my $ver = do {
104 8 100 100     65 if ($arg eq 'fc' || $arg eq 'unicode_eval' || $arg eq 'current_sub') {
    100 100        
    100          
    50          
105 3         7 $VERSION_5_016;
106             } elsif ($arg eq 'unicode_strings') {
107 1         3 $VERSION_5_012;
108             } elsif ($arg eq 'experimental::lexical_subs') {
109 1         3 $VERSION_5_018;
110             } elsif ($arg =~ /\A:5\.(.*)\z/) {
111 3         31 version->new("v5.$1");
112             } else {
113 0         0 $VERSION_5_010;
114             }
115             };
116 8         17 $test->('use feature' => $ver);
117             } else {
118 1         11 $test->('use feature' => $VERSION_5_010);
119             }
120             } else {
121 2         6 $test->('use feature' => $VERSION_5_010);
122             }
123             } elsif ($next_token->{data} eq 'utf8') {
124 1         3 $test->('utf8 pragma included in 5.6. Broken until 5.8' => $VERSION_5_008);
125             }
126             }
127             } elsif ($token->{name} eq 'DefaultOperator') {
128 4 50 33     18 if ($token->{data} eq '//' && $i >= 1) {
129 4         11 my $prev_token = $tokens[$i-1];
130 4 100 66     11 unless (
      66        
131             ($prev_token->name eq 'BuiltinFunc' && $prev_token->data =~ m{\A(?:split|grep|map)\z})
132             || $prev_token->name eq 'LeftParenthesis') {
133 2         44 $test->('// operator' => $VERSION_5_010);
134             }
135             }
136             } elsif ($token->{name} eq 'PolymorphicCompare') {
137 1 50       4 if ($token->{data} eq '~~') {
138 1         4 $test->('~~ operator' => $VERSION_5_010);
139             }
140             } elsif ($token->{name} eq 'DefaultEqual') {
141 1 50       5 if ($token->{data} eq '//=') {
142 1         4 $test->('//= operator' => $VERSION_5_010);
143             }
144             } elsif ($token->{name} eq 'GlobalHashVar') {
145 3 100 100     14 if ($token->{data} eq '%-' || $token->{data} eq '%+') {
146 2         10 $test->('%-/%+' => $VERSION_5_010);
147             }
148             } elsif ($token->{name} eq 'SpecificValue') {
149             # $-{"a"}
150             # $+{"a"}
151 4 100 100     35 if ($token->{data} eq '$-' || $token->{data} eq '$+') {
152 2         6 $test->('%-/%+' => $VERSION_5_010);
153             }
154             } elsif ($token->{name} eq 'GlobalArrayVar') {
155 10 100 100     46 if ($token->{data} eq '@-' || $token->{data} eq '@+') {
156 2         5 $test->('%-/%+' => $VERSION_5_010);
157             }
158             } elsif ($token->{name} eq 'WhenStmt') {
159 8 100 100     69 if ($i >= 1 && (
      100        
      100        
160             $tokens[$i-1]->{name} ne 'SemiColon'
161             && $tokens[$i-1]->{name} ne 'RightBrace'
162             && $tokens[$i-1]->{name} ne 'LeftBrace'
163             )) {
164 3         6 $test->("postfix when" => $VERSION_5_012);
165             } else {
166 5         16 $test->("normal when" => $VERSION_5_010);
167             }
168             } elsif ($token->{name} eq 'BuiltinFunc') {
169 81 100 100     234 if ($token->data eq 'each' || $token->data eq 'keys' || $token->data eq 'values') {
      100        
170 15         197 my $func = $token->data;
171 15 50       94 if (@tokens >= $i+1) {
172 15         29 my $next_token = $tokens[$i+1];
173 15 100 100     32 if ($next_token->name eq 'GlobalVar' || $next_token->name eq 'Var') {
    100 100        
174             # each $hashref
175             # each $arrayref
176 6         61 $test->("$func \$hashref, $func \$arrayref" => $VERSION_5_014);
177             } elsif ($next_token->name eq 'GlobalArrayVar' || $next_token->name eq 'ArrayVar') {
178 7         132 $test->("$func \@array" => $VERSION_5_012);
179             }
180             }
181             }
182 81 100 100     1304 if ($token->data eq 'push' || $token->data eq 'unshift' || $token->data eq 'pop' || $token->data eq 'shift' || $token->data eq 'splice') {
      100        
      100        
      100        
183 35         606 my $func = $token->data;
184 35 50       215 if (@tokens >= $i+1) {
185 35         55 my $offset = 1;
186 35         45 my $next_token;
187 35         56 do {
188 47         164 $next_token = $tokens[$i+$offset++];
189             } while $next_token->name eq 'LeftParenthesis';
190 35 100 100     257 if ($next_token->name eq 'GlobalVar' || $next_token->name eq 'Var') {
191             # shift $arrayref
192             # shift($arrayref, ...)
193 21         191 $test->("$func \$arrayref" => $VERSION_5_014);
194             }
195             }
196             }
197 81 100 66     1443 if ($token->data eq 'pack' || $token->data eq 'unpack') {
198 1 50 33     15 if (@tokens >= $i+1 and my $next_token = $tokens[$i+1]) {
199 1 50 33     7 if ($next_token->{name} eq 'String' && $next_token->data =~ m/[<>]/) {
200 1         30 $test->($token->data." uses < or >" => $VERSION_5_010);
201             }
202             }
203             }
204             } elsif ($token->{name} eq 'PostDeref' || $token->{name} eq 'PostDerefStar') {
205 0         0 $test->("postfix dereference" => $VERSION_5_020);
206             }
207             }
208 115         632 return $syntax_version;
209             }
210              
211             sub minimum_version {
212 109     109 1 428 my $self = shift;
213             return $self->{minimum_explicit_version} > $self->{minimum_syntax_version}
214             ? $self->{minimum_explicit_version}
215 109 100       1086 : $self->{minimum_syntax_version};
216             }
217              
218             sub minimum_syntax_version {
219 3     3 1 7 my $self = shift;
220 3         13 return $self->{minimum_syntax_version};
221             }
222              
223             sub minimum_explicit_version {
224 111     111 1 165 my $self = shift;
225 111         307 return $self->{minimum_explicit_version};
226             }
227              
228             sub version_markers {
229 108     108 1 184 my $self = shift;
230              
231 108 100       222 if ( my $explicit = $self->minimum_explicit_version ) {
232 3         15 $self->{version_markers}->{$explicit} = [ 'explicit' ];
233             }
234              
235 108         174 my @rv;
236              
237 108         160 foreach my $ver ( sort { version->new($a) <=> version->new($b) } keys %{$self->{version_markers}} ) {
  3         40  
  108         443  
238 78         583 push @rv, version->new($ver) => $self->{version_markers}->{$ver};
239             }
240              
241 108         341 return @rv;
242             }
243              
244             1;
245             __END__