File Coverage

blib/lib/Perl/PrereqScanner/Lite.pm
Criterion Covered Total %
statement 180 181 99.4
branch 84 90 93.3
condition 53 54 98.1
subroutine 15 15 100.0
pod 6 7 85.7
total 338 347 97.4


line stmt bran cond sub pod time code
1             package Perl::PrereqScanner::Lite;
2 11     11   39601 use 5.008005;
  11         97  
3 11     11   50 use strict;
  11         18  
  11         177  
4 11     11   42 use warnings;
  11         21  
  11         267  
5 11     11   44 use Carp ();
  11         17  
  11         198  
6 11     11   2467 use Compiler::Lexer;
  11         51810  
  11         581  
7 11     11   3751 use CPAN::Meta::Requirements;
  11         54974  
  11         319  
8 11     11   3397 use Perl::PrereqScanner::Lite::Constants;
  11         28  
  11         13007  
9              
10             our $VERSION = "0.28";
11              
12             sub new {
13 13     13 1 25831 my ($class, $opt) = @_;
14              
15 13         24 my $lexer;
16 13 100       45 if ($opt->{no_prereq}) {
17 3         26 $lexer = Compiler::Lexer->new({verbose => 1}),
18             }
19             else {
20 10         63 $lexer = Compiler::Lexer->new(),
21             }
22              
23 13         334 my $extra_scanners = [];
24 13 100       52 if (my $scanner_names = $opt->{extra_scanners}) {
25 3 100       10 if (ref $scanner_names eq 'ARRAY') {
26 2         4 for my $scanner_name (@$scanner_names) {
27 2         4 my $extra_scanner;
28 2 100       5 if (substr($scanner_name, 0, 1) eq '+') {
29 1         2 $extra_scanner = substr $scanner_name, 1;
30             }
31             else {
32 1         4 $extra_scanner = "Perl::PrereqScanner::Lite::Scanner::$scanner_name";
33             }
34              
35 2         149 eval "require $extra_scanner"; ## no critic
36 2         10 push @$extra_scanners, $extra_scanner;
37             }
38             } else {
39 1         176 Carp::croak "'extra_scanners' option must be array reference";
40             }
41             }
42              
43             bless {
44 12         92 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 90 my ($self, $scanner_name) = @_;
52              
53 4         6 my $extra_scanner;
54 4 100       15 if (substr($scanner_name, 0, 1) eq '+') {
55 1         2 $extra_scanner = substr $scanner_name, 1;
56             }
57             else {
58 3         8 $extra_scanner = "Perl::PrereqScanner::Lite::Scanner::$scanner_name";
59             }
60              
61 4         231 eval "require $extra_scanner"; ## no critic
62 4         13 push @{$self->{extra_scanners}}, $extra_scanner;
  4         28  
63             }
64              
65             sub scan_string {
66 12     12 1 110 my ($self, $string) = @_;
67              
68 12         8515 my $tokens = $self->{lexer}->tokenize($string);
69 12         173 $self->_scan($tokens);
70             }
71              
72             sub scan_file {
73 11     11 1 12222 my ($self, $file) = @_;
74              
75 11 50       334 open my $fh, '<', $file or die "Cannot open file: $file";
76 11         25 my $script = do { local $/; <$fh>; };
  11         38  
  11         167  
77              
78 11         44 $self->scan_string($script);
79             }
80              
81             sub scan_tokens {
82 1     1 1 24 my ($self, $tokens) = @_;
83 1         5 $self->_scan($tokens);
84             }
85              
86             sub scan_module {
87 1     1 1 24 my ($self, $module) = @_;
88              
89 1         287 require Module::Path;
90              
91 1 50       473 if (defined(my $path = Module::Path::module_path($module))) {
92 1         64 return $self->scan_file($path);
93             }
94             }
95              
96             sub _scan {
97 13     13   37 my ($self, $tokens) = @_;
98              
99 13         26 my $module_name = '';
100 13         24 my $module_version = 0;
101              
102 13         23 my $not_decl_module_name = '';
103              
104 13         24 my $is_in_reglist = 0;
105 13         19 my $is_in_usedecl = 0;
106 13         21 my $is_in_reqdecl = 0;
107 13         22 my $is_inherited = 0;
108 13         21 my $is_in_list = 0;
109 13         23 my $is_version_decl = 0;
110 13         24 my $is_aliased = 0;
111 13         23 my $is_prev_version = 0;
112 13         23 my $is_prev_module_name = 0;
113              
114 13         21 my $does_garbage_exist = 0;
115 13         19 my $does_use_lib_or_constant = 0;
116              
117 13         23 my $latest_prereq = '';
118              
119             TOP:
120 13         89 for (my $i = 0; my $token = $tokens->[$i]; $i++) {
121 3902         5191 my $token_type = $token->{type};
122              
123             # For require statement
124 3902 100 100     8360 if ($token_type == REQUIRE_DECL || ($token_type == BUILTIN_FUNC && $token->{data} eq 'no')) {
      100        
125 30         59 $is_in_reqdecl = 1;
126 30         57 next;
127             }
128 3872 100       5447 if ($is_in_reqdecl) {
129             # e.g.
130             # require Foo;
131 100 100 100     243 if ($token_type == REQUIRED_NAME || $token_type == KEY) {
132 11         30 $latest_prereq = $self->add_minimum($token->{data} => 0);
133              
134 11         64 $is_in_reqdecl = 0;
135 11         29 next;
136             }
137              
138             # e.g.
139             # require Foo::Bar;
140 89 100 100     201 if ($token_type == NAMESPACE || $token_type == NAMESPACE_RESOLVER) {
141 21         34 $module_name .= $token->{data};
142 21         39 next;
143             }
144              
145             # End of declare of require statement
146 68 100       104 if ($token_type == SEMI_COLON) {
147 19 100       31 if ($module_name) {
148 7         17 $latest_prereq = $self->add_minimum($module_name => 0);
149             }
150              
151 19         51 $module_name = '';
152 19         57 $is_in_reqdecl = 0;
153 19         37 next;
154             }
155              
156 49         86 next;
157             }
158              
159             # For use statement
160 3772 100       5253 if ($token_type == USE_DECL) {
161 104         139 $is_in_usedecl = 1;
162 104         202 next;
163             }
164 3668 100       5197 if ($is_in_usedecl) {
165             # e.g.
166             # use Foo;
167             # use parent qw/Foo/;
168             #
169 634 100 100     1435 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         110 $module_name = $token->{data};
177              
178 72 100 100     405 if ($module_name eq 'lib' || $module_name eq 'constant') {
    100          
    100          
179 6         15 $latest_prereq = $self->add_minimum($module_name, 0);
180 6         11 $does_use_lib_or_constant = 1;
181             }
182             elsif ($module_name =~ /(?:base|parent)/) {
183 24         36 $is_inherited = 1;
184             }
185             elsif ($module_name =~ 'aliased') {
186 6         11 $is_aliased = 1;
187             }
188              
189 72         126 $is_prev_module_name = 1;
190 72         171 next;
191             }
192              
193             # End of declare of use statement
194 562 100 100     1555 if ($token_type == SEMI_COLON || $token_type == LEFT_BRACE || $token_type == LEFT_BRACKET) {
      100        
195 100 100 66     256 if ($module_name && !$does_use_lib_or_constant) {
196 94         175 $latest_prereq = $self->add_minimum($module_name => $module_version);
197             }
198              
199 100         143 $module_name = '';
200 100         123 $module_version = 0;
201 100         124 $is_in_reglist = 0;
202 100         121 $is_inherited = 0;
203 100         119 $is_in_list = 0;
204 100         118 $is_in_usedecl = 0;
205 100         112 $is_aliased = 0;
206 100         114 $does_garbage_exist = 0;
207 100         119 $is_prev_module_name = 0;
208 100         117 $does_use_lib_or_constant = 0;
209              
210 100         211 next;
211             }
212              
213             # e.g.
214             # use Foo::Bar;
215 462 100 100     1016 if ($token_type == NAMESPACE || $token_type == NAMESPACE_RESOLVER) {
216 96         154 $module_name .= $token->{data};
217 96         144 $is_prev_module_name = 1;
218 96         180 next;
219             }
220              
221             # Section for parent/base
222 366 100       529 if ($is_inherited) {
223             # For qw() notation
224             # e.g.
225             # use parent qw/Foo Bar/;
226 96 100 100     279 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       21 if ($token_type == REG_EXP) {
231 6         30 for my $_module_name (split /\s+/, $token->{data}) {
232 12         29 $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         9 $is_in_list = 1;
243             }
244             elsif ($token_type == RIGHT_PAREN) {
245 6         9 $is_in_list = 0;
246             }
247             elsif ($is_in_list) {
248 24 100 100     70 if ($token_type == STRING || $token_type == RAW_STRING) {
249 12         28 $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         27 $latest_prereq = $self->add_minimum($token->{data} => 0);
258             }
259              
260 96         123 $is_prev_module_name = 0;
261 96         176 next;
262             }
263              
264 270 100 100     801 if ($token_type == DOUBLE || $token_type == INT || $token_type == VERSION_STRING) {
      100        
265 22 100       65 if (!$module_name) {
    100          
266 4 50       13 if (!$does_garbage_exist) {
267             # For perl version
268             # e.g.
269             # use 5.012;
270 4         11 my $perl_version = $token->{data};
271 4         13 $latest_prereq = $self->add_minimum('perl' => $perl_version);
272 4         9 $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         24 $module_version = $token->{data};
282             }
283              
284 22         31 $is_prev_module_name = 0;
285 22         29 $is_prev_version = 1;
286 22         45 next;
287             }
288              
289 248 100       365 if ($is_aliased) {
290 12 100 100     35 if ($token_type == STRING || $token_type == RAW_STRING) {
291 6         18 $latest_prereq = $self->add_minimum($token->{data} => 0);
292 6         12 $is_aliased = 0;
293             }
294 12         26 next;
295             }
296              
297 236 100 100     626 if (($is_prev_module_name || $is_prev_version) && $token_type == LEFT_PAREN) {
      100        
298 12         20 my $left_paren_num = 1;
299 12         32 for ($i++; $token = $tokens->[$i]; $i++) { # skip content that is surrounded by parens
300 372         455 $token_type = $token->{type};
301              
302 372 50       739 if ($token_type == LEFT_PAREN) {
    100          
303 0         0 $left_paren_num++;
304             }
305             elsif ($token_type == RIGHT_PAREN) {
306 12 50       30 last if --$left_paren_num <= 0;
307             }
308             }
309 12         26 next;
310             }
311              
312 224 100       339 if ($token_type != WHITESPACE) {
313 66         77 $does_garbage_exist = 1;
314 66         74 $is_prev_module_name = 0;
315 66         74 $is_prev_version = 0;
316             }
317 224         378 next;
318             }
319              
320 3034         3260 for my $extra_scanner (@{$self->{extra_scanners}}) {
  3034         4235  
321 233 100       444 if ($extra_scanner->scan($self, $token, $token_type)) {
322 208         432 next TOP;
323             }
324             }
325              
326 2826 100 100     6266 if ($token_type == COMMENT && $token->{data} =~ /\A##\s*no prereq\Z/) {
327 3         17 $self->{module_reqs}->clear_requirement($latest_prereq);
328 3         48 next;
329             }
330             }
331              
332 13         1621 return $self->{module_reqs};
333             }
334              
335             sub add_minimum {
336 207     207 0 378 my ($self, $module_name, $module_version) = @_;
337              
338 207 50       315 if ($module_name) {
339 207         439 $self->{module_reqs}->add_minimum($module_name => $module_version);
340             }
341              
342 207         6302 return $module_name;
343             }
344              
345             1;
346             __END__