File Coverage

blib/lib/Perl/PrereqScanner/Lite.pm
Criterion Covered Total %
statement 181 182 99.4
branch 84 90 93.3
condition 52 54 96.3
subroutine 15 15 100.0
pod 6 7 85.7
total 338 348 97.1


line stmt bran cond sub pod time code
1             package Perl::PrereqScanner::Lite;
2 11     11   39380 use 5.008005;
  11         28  
  11         353  
3 11     11   40 use strict;
  11         13  
  11         322  
4 11     11   45 use warnings;
  11         13  
  11         228  
5 11     11   37 use Carp ();
  11         12  
  11         150  
6 11     11   4584 use Compiler::Lexer;
  11         53434  
  11         629  
7 11     11   6371 use CPAN::Meta::Requirements;
  11         48050  
  11         302  
8 11     11   4164 use Perl::PrereqScanner::Lite::Constants;
  11         22  
  11         15569  
9              
10             our $VERSION = "0.26";
11              
12             sub new {
13 13     13 1 21894 my ($class, $opt) = @_;
14              
15 13         24 my $lexer;
16 13 100       58 if ($opt->{no_prereq}) {
17 3         40 $lexer = Compiler::Lexer->new({verbose => 1}),
18             }
19             else {
20 10         80 $lexer = Compiler::Lexer->new(),
21             }
22              
23 13         376 my $extra_scanners = [];
24 13 100       57 if (my $scanner_names = $opt->{extra_scanners}) {
25 3 100       9 if (ref $scanner_names eq 'ARRAY') {
26 2         7 for my $scanner_name (@$scanner_names) {
27 2         3 my $extra_scanner;
28 2 100       53 if (substr($scanner_name, 0, 1) eq '+') {
29 1         3 $extra_scanner = substr $scanner_name, 1;
30             }
31             else {
32 1         4 $extra_scanner = "Perl::PrereqScanner::Lite::Scanner::$scanner_name";
33             }
34              
35 2         163 eval "require $extra_scanner"; ## no critic
36 2         8 push @$extra_scanners, $extra_scanner;
37             }
38             } else {
39 1         157 Carp::croak "'extra_scanners' option must be array reference";
40             }
41             }
42              
43             bless {
44 12         157 lexer => $lexer,
45             extra_scanners => $extra_scanners,
46             module_reqs => CPAN::Meta::Requirements->new,
47             }, $class;
48             }
49              
50             sub add_extra_scanner {
51 4     4 1 95 my ($self, $scanner_name) = @_;
52              
53 4         5 my $extra_scanner;
54 4 100       17 if (substr($scanner_name, 0, 1) eq '+') {
55 1         4 $extra_scanner = substr $scanner_name, 1;
56             }
57             else {
58 3         8 $extra_scanner = "Perl::PrereqScanner::Lite::Scanner::$scanner_name";
59             }
60              
61 4         521 eval "require $extra_scanner"; ## no critic
62 4         15 push @{$self->{extra_scanners}}, $extra_scanner;
  4         33  
63             }
64              
65             sub scan_string {
66 12     12 1 145 my ($self, $string) = @_;
67              
68 12         10909 my $tokens = $self->{lexer}->tokenize($string);
69 12         242 $self->_scan($tokens);
70             }
71              
72             sub scan_file {
73 11     11 1 12640 my ($self, $file) = @_;
74              
75 11 50       563 open my $fh, '<', $file or die "Cannot open file: $file";
76 11         18 my $script = do { local $/; <$fh>; };
  11         37  
  11         222  
77              
78 11         45 $self->scan_string($script);
79             }
80              
81             sub scan_tokens {
82 1     1 1 34 my ($self, $tokens) = @_;
83 1         6 $self->_scan($tokens);
84             }
85              
86             sub scan_module {
87 1     1 1 27 my ($self, $module) = @_;
88              
89 1         734 require Module::Path;
90              
91 1 50       571 if (defined(my $path = Module::Path::module_path($module))) {
92 1         81 return $self->scan_file($path);
93             }
94             }
95              
96             sub _scan {
97 13     13   24 my ($self, $tokens) = @_;
98              
99 13         25 my $module_name = '';
100 13         17 my $module_version = 0;
101              
102 13         22 my $not_decl_module_name = '';
103              
104 13         20 my $is_in_reglist = 0;
105 13         33 my $is_in_usedecl = 0;
106 13         42 my $is_in_reqdecl = 0;
107 13         14 my $is_inherited = 0;
108 13         21 my $is_in_list = 0;
109 13         15 my $is_version_decl = 0;
110 13         15 my $is_aliased = 0;
111 13         18 my $is_prev_version = 0;
112 13         11 my $is_prev_module_name = 0;
113              
114 13         16 my $does_garbage_exist = 0;
115 13         17 my $does_use_lib_or_constant = 0;
116              
117 13         18 my $latest_prereq = '';
118              
119             TOP:
120 13         100 for (my $i = 0; my $token = $tokens->[$i]; $i++) {
121 3902         3572 my $token_type = $token->{type};
122              
123             # For require statement
124 3902 100 100     8708 if ($token_type == REQUIRE_DECL || ($token_type == BUILTIN_FUNC && $token->{data} eq 'no')) {
      66        
125 30         29 $is_in_reqdecl = 1;
126 30         58 next;
127             }
128 3872 100       4828 if ($is_in_reqdecl) {
129             # e.g.
130             # require Foo;
131 100 100 100     292 if ($token_type == REQUIRED_NAME || $token_type == KEY) {
132 11         28 $latest_prereq = $self->add_minimum($token->{data} => 0);
133              
134 11         14 $is_in_reqdecl = 0;
135 11         26 next;
136             }
137              
138             # e.g.
139             # require Foo::Bar;
140 89 100 100     220 if ($token_type == NAMESPACE || $token_type == NAMESPACE_RESOLVER) {
141 21         26 $module_name .= $token->{data};
142 21         38 next;
143             }
144              
145             # End of declare of require statement
146 68 100       93 if ($token_type == SEMI_COLON) {
147 19 100       56 if ($module_name) {
148 7         16 $latest_prereq = $self->add_minimum($module_name => 0);
149             }
150              
151 19         21 $module_name = '';
152 19         16 $is_in_reqdecl = 0;
153 19         44 next;
154             }
155              
156 49         80 next;
157             }
158              
159             # For use statement
160 3772 100       4631 if ($token_type == USE_DECL) {
161 104         92 $is_in_usedecl = 1;
162 104         196 next;
163             }
164 3668 100       4406 if ($is_in_usedecl) {
165             # e.g.
166             # use Foo;
167             # use parent qw/Foo/;
168             #
169 634 100 100     1694 if ($token_type == USED_NAME || $token_type == IF_STMT) {
170             # XXX ~~~~~~~~~~~~~~~~~~~~~~
171             # Workaround for `use if` statement
172             # It is a matter of Compiler::Lexer (maybe).
173             #
174             # use if $] < 5.009_005, 'MRO::Compat';
175              
176 72         95 $module_name = $token->{data};
177              
178 72 100 100     521 if ($module_name eq 'lib' || $module_name eq 'constant') {
    100          
    100          
179 6         19 $latest_prereq = $self->add_minimum($module_name, 0);
180 6         9 $does_use_lib_or_constant = 1;
181             }
182             elsif ($module_name =~ /(?:base|parent)/) {
183 24         22 $is_inherited = 1;
184             }
185             elsif ($module_name =~ 'aliased') {
186 6         10 $is_aliased = 1;
187             }
188              
189 72         59 $is_prev_module_name = 1;
190 72         135 next;
191             }
192              
193             # End of declare of use statement
194 562 100 100     2033 if ($token_type == SEMI_COLON || $token_type == LEFT_BRACE || $token_type == LEFT_BRACKET) {
      100        
195 100 100 66     314 if ($module_name && !$does_use_lib_or_constant) {
196 94         169 $latest_prereq = $self->add_minimum($module_name => $module_version);
197             }
198              
199 100         105 $module_name = '';
200 100         85 $module_version = 0;
201 100         78 $is_in_reglist = 0;
202 100         77 $is_inherited = 0;
203 100         76 $is_in_list = 0;
204 100         72 $is_in_usedecl = 0;
205 100         84 $is_aliased = 0;
206 100         78 $does_garbage_exist = 0;
207 100         70 $is_prev_module_name = 0;
208 100         65 $does_use_lib_or_constant = 0;
209              
210 100         252 next;
211             }
212              
213             # e.g.
214             # use Foo::Bar;
215 462 100 100     1349 if ($token_type == NAMESPACE || $token_type == NAMESPACE_RESOLVER) {
216 96         104 $module_name .= $token->{data};
217 96         75 $is_prev_module_name = 1;
218 96         178 next;
219             }
220              
221             # Section for parent/base
222 366 100       448 if ($is_inherited) {
223             # For qw() notation
224             # e.g.
225             # use parent qw/Foo Bar/;
226 96 100 100     341 if ($token_type == REG_LIST) {
    100          
    100          
    100          
    100          
    100          
227 6         10 $is_in_reglist = 1;
228             }
229             elsif ($is_in_reglist) {
230 12 100       24 if ($token_type == REG_EXP) {
231 6         23 for my $_module_name (split /\s+/, $token->{data}) {
232 12         32 $latest_prereq = $self->add_minimum($_module_name => 0);
233             }
234 6         10 $is_in_reglist = 0;
235             }
236             }
237              
238             # For simply list
239             # e.g.
240             # use parent ('Foo' 'Bar');
241             elsif ($token_type == LEFT_PAREN) {
242 6         21 $is_in_list = 1;
243             }
244             elsif ($token_type == RIGHT_PAREN) {
245 6         10 $is_in_list = 0;
246             }
247             elsif ($is_in_list) {
248 24 100 100     82 if ($token_type == STRING || $token_type == RAW_STRING) {
249 12         43 $latest_prereq = $self->add_minimum($token->{data} => 0);
250             }
251             }
252              
253             # For string
254             # e.g.
255             # use parent "Foo"
256             elsif ($token_type == STRING || $token_type == RAW_STRING) {
257 12         30 $latest_prereq = $self->add_minimum($token->{data} => 0);
258             }
259              
260 96         83 $is_prev_module_name = 0;
261 96         281 next;
262             }
263              
264 270 100 100     1174 if ($token_type == DOUBLE || $token_type == INT || $token_type == VERSION_STRING) {
      100        
265 22 100       54 if (!$module_name) {
    100          
266 4 50       11 if (!$does_garbage_exist) {
267             # For perl version
268             # e.g.
269             # use 5.012;
270 4         8 my $perl_version = $token->{data};
271 4         17 $latest_prereq = $self->add_minimum('perl' => $perl_version);
272 4         7 $is_in_usedecl = 0;
273             }
274             }
275             elsif($is_prev_module_name) {
276             # For module version
277             # e.g.
278             # use Foo::Bar 0.0.1;'
279             # use Foo::Bar v0.0.1;
280             # use Foo::Bar 0.0_1;
281 12         21 $module_version = $token->{data};
282             }
283              
284 22         25 $is_prev_module_name = 0;
285 22         19 $is_prev_version = 1;
286 22         42 next;
287             }
288              
289 248 100       312 if ($is_aliased) {
290 12 100 100     59 if ($token_type == STRING || $token_type == RAW_STRING) {
291 6         16 $latest_prereq = $self->add_minimum($token->{data} => 0);
292 6         9 $is_aliased = 0;
293             }
294 12         37 next;
295             }
296              
297 236 100 100     851 if (($is_prev_module_name || $is_prev_version) && $token_type == LEFT_PAREN) {
      100        
298 12         13 my $left_paren_num = 1;
299 12         36 for ($i++; $token = $tokens->[$i]; $i++) { # skip content that is surrounded by parens
300 372         259 $token_type = $token->{type};
301              
302 372 50       854 if ($token_type == LEFT_PAREN) {
    100          
303 0         0 $left_paren_num++;
304             }
305             elsif ($token_type == RIGHT_PAREN) {
306 12 50       35 last if --$left_paren_num <= 0;
307             }
308             }
309 12         23 next;
310             }
311              
312 224 100       296 if ($token_type != WHITESPACE) {
313 66         43 $does_garbage_exist = 1;
314 66         108 $is_prev_module_name = 0;
315 66         54 $is_prev_version = 0;
316             }
317 224         464 next;
318             }
319              
320 3034         1968 for my $extra_scanner (@{$self->{extra_scanners}}) {
  3034         3470  
321 233 100       493 if ($extra_scanner->scan($self, $token, $token_type)) {
322 208         447 next TOP;
323             }
324             }
325              
326 2826 100 100     6891 if ($token_type == COMMENT && $token->{data} =~ /\A##\s*no prereq\Z/) {
327 3         14 $self->{module_reqs}->clear_requirement($latest_prereq);
328 3         50 next;
329             }
330             }
331              
332 13         2087 return $self->{module_reqs};
333             }
334              
335             sub add_minimum {
336 207     207 0 252 my ($self, $module_name, $module_version) = @_;
337              
338 207 50       314 if ($module_name) {
339 207         477 $self->{module_reqs}->add_minimum($module_name => $module_version);
340             }
341              
342 207         8521 return $module_name;
343             }
344              
345             1;
346             __END__