File Coverage

blib/lib/Perl/MinimumVersion/Fast.pm
Criterion Covered Total %
statement 136 138 98.5
branch 88 100 88.0
condition 83 95 87.3
subroutine 14 14 100.0
pod 5 5 100.0
total 326 352 92.6


line stmt bran cond sub pod time code
1             package Perl::MinimumVersion::Fast;
2 6     6   123726 use 5.008005;
  6         21  
3 6     6   30 use strict;
  6         12  
  6         132  
4 6     6   36 use warnings;
  6         13  
  6         186  
5              
6 6     6   4199 use version ();
  6         26068  
  6         187  
7              
8 6     6   4072 use Compiler::Lexer 0.13;
  6         223747  
  6         646  
9 6     6   70 use List::Util qw(max);
  6         15  
  6         13759  
10              
11             our $VERSION = "0.18";
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 10740 my ($class, $stuff) = @_;
24              
25 115         130 my $filename;
26             my $src;
27 115 100       246 if (ref $stuff ne 'SCALAR') {
28 1         2 $filename = $stuff;
29 1 50       43 open my $fh, '<', $filename
30             or die "Unknown file: $filename";
31 1         1 $src = do { local $/; <$fh> };
  1         4  
  1         38  
32             } else {
33 114         126 $filename = '-';
34 114         177 $src = $$stuff;
35             }
36              
37 115         341 my $lexer = Compiler::Lexer->new($filename);
38 115         13332 my @tokens = $lexer->tokenize($src);
39              
40 115         281 my $self = bless { }, $class;
41 115         264 $self->{minimum_explicit_version} = $self->_build_minimum_explicit_version(\@tokens);
42 115         294 $self->{minimum_syntax_version} = $self->_build_minimum_syntax_version(\@tokens);
43 115         1084 $self;
44             }
45              
46             sub _build_minimum_explicit_version {
47 115     115   156 my ($self, $tokens) = @_;
48 115         121 my @tokens = map { @$_ } @{$tokens};
  115         368  
  115         199  
49              
50 115         151 my $explicit_version;
51 115         258 for my $i (0..@tokens-1) {
52 871 100 100     12424 if ($tokens[$i]->{name} eq 'UseDecl' || $tokens[$i]->{name} eq 'RequireDecl') {
53 30 50       71 if (@tokens >= $i+1) {
54 30         38 my $next_token = $tokens[$i+1];
55 30 100 100     141 if ($next_token->{name} eq 'Double' or $next_token->{name} eq 'VersionString') {
56 7   50     154 $explicit_version = max($explicit_version || 0, version->new($next_token->{data}));
57             }
58             }
59             }
60             }
61 115         343 return $explicit_version;
62             }
63              
64             sub _build_minimum_syntax_version {
65 115     115   149 my ($self, $tokens) = @_;
66 115         119 my @tokens = map { @$_ } @{$tokens};
  115         368  
  115         163  
67 115         171 my $syntax_version = $MIN_VERSION;
68              
69             my $test = sub {
70 79     79   362 my ($reason, $version) = @_;
71 79         416 $syntax_version = max($syntax_version, $version);
72 79         96 push @{$self->{version_markers}->{$version}}, $reason;
  79         954  
73 115         477 };
74              
75 115         536 for my $i (0..@tokens-1) {
76 871         2202 my $token = $tokens[$i];
77 871 100 100     14421 if ($token->{name} eq 'ToDo') {
    100 33        
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    50          
78             # ... => 5.12
79 3         9 $test->('yada-yada-yada operator(...)' => $VERSION_5_012);
80             } elsif ($token->{name} eq 'Package') {
81 12 100 100     61 if (@tokens > $i+2 && $tokens[$i+1]->name eq 'Class') {
82 10         72 my $number = $tokens[$i+2];
83 10 100 100     64 if ($number->{name} eq 'Int' || $number->{name} eq 'Double' || $number->{name} eq 'VersionString') {
    100 100        
84             # package NAME VERSION; => 5.012
85 7         14 $test->('package NAME VERSION' => $VERSION_5_012);
86              
87 7 100 66     36 if (@tokens > $i+3 && $tokens[$i+3]->{name} eq 'LeftBrace') {
88 3         14 $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       75 if (@tokens >= $i+1) {
96             # use feature => 5.010
97 30         41 my $next_token = $tokens[$i+1];
98 30 100       347 if ($next_token->{data} eq 'feature') {
    100          
99 11 100       23 if (@tokens > $i+2) {
100 9         11 my $next_token = $tokens[$i+2];
101 9 100       27 if ($next_token->name eq 'String') {
102 8         53 my $arg = $next_token->data;
103 8         35 my $ver = do {
104 8 100 100     61 if ($arg eq 'fc' || $arg eq 'unicode_eval' || $arg eq 'current_sub') {
    100 100        
    100          
    50          
105 3         5 $VERSION_5_016;
106             } elsif ($arg eq 'unicode_strings') {
107 1         2 $VERSION_5_012;
108             } elsif ($arg eq 'experimental::lexical_subs') {
109 1         3 $VERSION_5_018;
110             } elsif ($arg =~ /\A:5\.(.*)\z/) {
111 3         26 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         8 $test->('use feature' => $VERSION_5_010);
119             }
120             } else {
121 2         5 $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 11 50 33     43 if ($token->{data} eq '//' && $i >= 1) {
129 11         16 my $prev_token = $tokens[$i-1];
130 11 100 100     27 unless (
      100        
131             ($prev_token->name eq 'BuiltinFunc' && $prev_token->data =~ m{\A(?:split|grep|map)\z})
132             || $prev_token->name eq 'LeftParenthesis') {
133 2         37 $test->('// operator' => $VERSION_5_010);
134             }
135             }
136             } elsif ($token->{name} eq 'PolymorphicCompare') {
137 1 50       4 if ($token->{data} eq '~~') {
138 1         3 $test->('~~ operator' => $VERSION_5_010);
139             }
140             } elsif ($token->{name} eq 'DefaultEqual') {
141 1 50       4 if ($token->{data} eq '//=') {
142 1         3 $test->('//= operator' => $VERSION_5_010);
143             }
144             } elsif ($token->{name} eq 'GlobalHashVar') {
145 3 100 100     15 if ($token->{data} eq '%-' || $token->{data} eq '%+') {
146 2         6 $test->('%-/%+' => $VERSION_5_010);
147             }
148             } elsif ($token->{name} eq 'SpecificValue') {
149             # $-{"a"}
150             # $+{"a"}
151 4 100 100     33 if ($token->{data} eq '$-' || $token->{data} eq '$+') {
152 2         5 $test->('%-/%+' => $VERSION_5_010);
153             }
154             } elsif ($token->{name} eq 'GlobalArrayVar') {
155 10 100 100     56 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     296 if ($i >= 1 && (
      100        
      66        
160             $tokens[$i-1]->{name} ne 'SemiColon'
161             && $tokens[$i-1]->{name} ne 'RightBrace'
162             && $tokens[$i-1]->{name} ne 'LeftBrace'
163             )) {
164 3         7 $test->("postfix when" => $VERSION_5_012);
165             } else {
166 5         14 $test->("normal when" => $VERSION_5_010);
167             }
168             } elsif ($token->{name} eq 'BuiltinFunc') {
169 81 100 100     236 if ($token->data eq 'each' || $token->data eq 'keys' || $token->data eq 'values') {
      100        
170 15         198 my $func = $token->data;
171 15 50       91 if (@tokens >= $i+1) {
172 15         18 my $next_token = $tokens[$i+1];
173 15 100 100     38 if ($next_token->name eq 'GlobalVar' || $next_token->name eq 'Var') {
    100 100        
174             # each $hashref
175             # each $arrayref
176 6         59 $test->("$func \$hashref, $func \$arrayref" => $VERSION_5_014);
177             } elsif ($next_token->name eq 'GlobalArrayVar' || $next_token->name eq 'ArrayVar') {
178 7         146 $test->("$func \@array" => $VERSION_5_012);
179             }
180             }
181             }
182 81 100 100     1441 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         749 my $func = $token->data;
184 35 50       199 if (@tokens >= $i+1) {
185 35         38 my $offset = 1;
186 35         55 my $next_token;
187 35         36 do {
188 47         185 $next_token = $tokens[$i+$offset++];
189             } while $next_token->name eq 'LeftParenthesis';
190 35 100 100     220 if ($next_token->name eq 'GlobalVar' || $next_token->name eq 'Var') {
191             # shift $arrayref
192             # shift($arrayref, ...)
193 21         199 $test->("$func \$arrayref" => $VERSION_5_014);
194             }
195             }
196             }
197 81 100 66     1601 if ($token->data eq 'pack' || $token->data eq 'unpack') {
198 1 50 33     36 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         13 $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         594 return $syntax_version;
209             }
210              
211             sub minimum_version {
212 109     109 1 352 my $self = shift;
213             return $self->{minimum_explicit_version} > $self->{minimum_syntax_version}
214             ? $self->{minimum_explicit_version}
215 109 100       902 : $self->{minimum_syntax_version};
216             }
217              
218             sub minimum_syntax_version {
219 3     3 1 4 my $self = shift;
220 3         13 return $self->{minimum_syntax_version};
221             }
222              
223             sub minimum_explicit_version {
224 111     111 1 130 my $self = shift;
225 111         281 return $self->{minimum_explicit_version};
226             }
227              
228             sub version_markers {
229 108     108 1 132 my $self = shift;
230              
231 108 100       164 if ( my $explicit = $self->minimum_explicit_version ) {
232 3         14 $self->{version_markers}->{$explicit} = [ 'explicit' ];
233             }
234              
235 108         122 my @rv;
236              
237 108         104 foreach my $ver ( sort { version->new($a) <=> version->new($b) } keys %{$self->{version_markers}} ) {
  3         35  
  108         368  
238 78         469 push @rv, version->new($ver) => $self->{version_markers}->{$ver};
239             }
240              
241 108         327 return @rv;
242             }
243              
244             1;
245             __END__