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   344391 use 5.008005;
  6         82  
3 6     6   34 use strict;
  6         17  
  6         116  
4 6     6   27 use warnings;
  6         13  
  6         132  
5              
6 6     6   2666 use version ();
  6         11488  
  6         195  
7              
8 6     6   2625 use Compiler::Lexer 0.13;
  6         39102  
  6         303  
9 6     6   45 use List::Util qw(max);
  6         12  
  6         11323  
10              
11             our $VERSION = "0.20";
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 31088 my ($class, $stuff) = @_;
24              
25 115         192 my $filename;
26             my $src;
27 115 100       322 if (ref $stuff ne 'SCALAR') {
28 1         4 $filename = $stuff;
29 1 50       56 open my $fh, '<', $filename
30             or die "Unknown file: $filename";
31 1         3 $src = do { local $/; <$fh> };
  1         15  
  1         66  
32             } else {
33 114         192 $filename = '-';
34 114         187 $src = $$stuff;
35             }
36              
37 115         394 my $lexer = Compiler::Lexer->new($filename);
38 115         11772 my @tokens = $lexer->tokenize($src);
39              
40 115         473 my $self = bless { }, $class;
41 115         310 $self->{minimum_explicit_version} = $self->_build_minimum_explicit_version(\@tokens);
42 115         272 $self->{minimum_syntax_version} = $self->_build_minimum_syntax_version(\@tokens);
43 115         1042 $self;
44             }
45              
46             sub _build_minimum_explicit_version {
47 115     115   308 my ($self, $tokens) = @_;
48 115         192 my @tokens = map { @$_ } @{$tokens};
  115         354  
  115         253  
49              
50 115         189 my $explicit_version;
51 115         374 for my $i (0..@tokens-1) {
52 885 100 100     2756 if ($tokens[$i]->{name} eq 'UseDecl' || $tokens[$i]->{name} eq 'RequireDecl') {
53 30 50       86 if (@tokens >= $i+1) {
54 30         69 my $next_token = $tokens[$i+1];
55 30 100 100     127 if ($next_token->{name} eq 'Double' or $next_token->{name} eq 'VersionString') {
56 7   50     148 $explicit_version = max($explicit_version || 0, version->new($next_token->{data}));
57             }
58             }
59             }
60             }
61 115         349 return $explicit_version;
62             }
63              
64             sub _build_minimum_syntax_version {
65 115     115   202 my ($self, $tokens) = @_;
66 115         171 my @tokens = map { @$_ } @{$tokens};
  115         289  
  115         190  
67 115         214 my $syntax_version = $MIN_VERSION;
68              
69             my $test = sub {
70 79     79   170 my ($reason, $version) = @_;
71 79         479 $syntax_version = max($syntax_version, $version);
72 79         130 push @{$self->{version_markers}->{$version}}, $reason;
  79         496  
73 115         506 };
74              
75 115         287 for my $i (0..@tokens-1) {
76 885         2128 my $token = $tokens[$i];
77 885 100 100     6285 if ($token->{name} eq 'ToDo') {
    100 33        
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    50          
78             # ... => 5.12
79 3         13 $test->('yada-yada-yada operator(...)' => $VERSION_5_012);
80             } elsif ($token->{name} eq 'Package') {
81 12 100 100     59 if (@tokens > $i+2 && $tokens[$i+1]->name eq 'Class') {
82 10         82 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         19 $test->('package NAME VERSION' => $VERSION_5_012);
86              
87 7 100 66     32 if (@tokens > $i+3 && $tokens[$i+3]->{name} eq 'LeftBrace') {
88 3         7 $test->('package NAME VERSION BLOCK' => $VERSION_5_014);
89             }
90             } elsif ($tokens[$i+2]->{name} eq 'LeftBrace') {
91 1         4 $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         54 my $next_token = $tokens[$i+1];
98 30 100       91 if ($next_token->{data} eq 'feature') {
    100          
99 11 100       25 if (@tokens > $i+2) {
100 9         14 my $next_token = $tokens[$i+2];
101 9 100       27 if ($next_token->name eq 'String') {
102 8         59 my $arg = $next_token->data;
103 8         43 my $ver = do {
104 8 100 100     54 if ($arg eq 'fc' || $arg eq 'unicode_eval' || $arg eq 'current_sub') {
    100 100        
    100          
    50          
105 3         8 $VERSION_5_016;
106             } elsif ($arg eq 'unicode_strings') {
107 1         3 $VERSION_5_012;
108             } elsif ($arg eq 'experimental::lexical_subs') {
109 1         4 $VERSION_5_018;
110             } elsif ($arg =~ /\A:5\.(.*)\z/) {
111 3         27 version->new("v5.$1");
112             } else {
113 0         0 $VERSION_5_010;
114             }
115             };
116 8         19 $test->('use feature' => $ver);
117             } else {
118 1         10 $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         4 $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         9 my $prev_token = $tokens[$i-1];
130 4 100 66     12 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         52 $test->('// operator' => $VERSION_5_010);
134             }
135             }
136             } elsif ($token->{name} eq 'PolymorphicCompare') {
137 1 50       4 if ($token->{data} eq '~~') {
138 1         2 $test->('~~ operator' => $VERSION_5_010);
139             }
140             } elsif ($token->{name} eq 'DefaultEqual') {
141 1 50       4 if ($token->{data} eq '//=') {
142 1         4 $test->('//= operator' => $VERSION_5_010);
143             }
144             } elsif ($token->{name} eq 'GlobalHashVar') {
145 3 100 100     12 if ($token->{data} eq '%-' || $token->{data} eq '%+') {
146 2         7 $test->('%-/%+' => $VERSION_5_010);
147             }
148             } elsif ($token->{name} eq 'SpecificValue') {
149             # $-{"a"}
150             # $+{"a"}
151 4 100 100     28 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     86 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         9 $test->("postfix when" => $VERSION_5_012);
165             } else {
166 5         13 $test->("normal when" => $VERSION_5_010);
167             }
168             } elsif ($token->{name} eq 'BuiltinFunc') {
169 81 100 100     242 if ($token->data eq 'each' || $token->data eq 'keys' || $token->data eq 'values') {
      100        
170 15         190 my $func = $token->data;
171 15 50       92 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         58 $test->("$func \$hashref, $func \$arrayref" => $VERSION_5_014);
177             } elsif ($next_token->name eq 'GlobalArrayVar' || $next_token->name eq 'ArrayVar') {
178 7         134 $test->("$func \@array" => $VERSION_5_012);
179             }
180             }
181             }
182 81 100 100     1412 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         609 my $func = $token->data;
184 35 50       216 if (@tokens >= $i+1) {
185 35         53 my $offset = 1;
186 35         51 my $next_token;
187 35         48 do {
188 47         163 $next_token = $tokens[$i+$offset++];
189             } while $next_token->name eq 'LeftParenthesis';
190 35 100 100     241 if ($next_token->name eq 'GlobalVar' || $next_token->name eq 'Var') {
191             # shift $arrayref
192             # shift($arrayref, ...)
193 21         186 $test->("$func \$arrayref" => $VERSION_5_014);
194             }
195             }
196             }
197 81 100 66     1844 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     5 if ($next_token->{name} eq 'String' && $next_token->data =~ m/[<>]/) {
200 1         18 $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         627 return $syntax_version;
209             }
210              
211             sub minimum_version {
212 109     109 1 438 my $self = shift;
213             return $self->{minimum_explicit_version} > $self->{minimum_syntax_version}
214             ? $self->{minimum_explicit_version}
215 109 100       1057 : $self->{minimum_syntax_version};
216             }
217              
218             sub minimum_syntax_version {
219 3     3 1 8 my $self = shift;
220 3         12 return $self->{minimum_syntax_version};
221             }
222              
223             sub minimum_explicit_version {
224 111     111 1 162 my $self = shift;
225 111         330 return $self->{minimum_explicit_version};
226             }
227              
228             sub version_markers {
229 108     108 1 173 my $self = shift;
230              
231 108 100       208 if ( my $explicit = $self->minimum_explicit_version ) {
232 3         15 $self->{version_markers}->{$explicit} = [ 'explicit' ];
233             }
234              
235 108         186 my @rv;
236              
237 108         188 foreach my $ver ( sort { version->new($a) <=> version->new($b) } keys %{$self->{version_markers}} ) {
  3         41  
  108         435  
238 78         574 push @rv, version->new($ver) => $self->{version_markers}->{$ver};
239             }
240              
241 108         347 return @rv;
242             }
243              
244             1;
245             __END__