File Coverage

blib/lib/Perl/PrereqScanner/Lite.pm
Criterion Covered Total %
statement 28 181 15.4
branch 3 90 3.3
condition 0 54 0.0
subroutine 8 15 53.3
pod 6 7 85.7
total 45 347 12.9


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