File Coverage

blib/lib/PLS/Parser/Index.pm
Criterion Covered Total %
statement 140 382 36.6
branch 16 106 15.0
condition 3 21 14.2
subroutine 28 50 56.0
pod 0 25 0.0
total 187 584 32.0


line stmt bran cond sub pod time code
1             package PLS::Parser::Index;
2              
3 11     11   65 use strict;
  11         15  
  11         277  
4 11     11   44 use warnings;
  11         13  
  11         224  
5              
6 11     11   53 use feature 'state';
  11         14  
  11         1033  
7              
8 11     11   60 use File::Find;
  11         14  
  11         641  
9 11     11   56 no warnings 'File::Find';
  11         22  
  11         417  
10 11     11   50 use File::Spec;
  11         78  
  11         305  
11 11     11   4278 use File::stat;
  11         62412  
  11         45  
12 11     11   5662 use IO::Async::Function;
  11         266038  
  11         330  
13 11     11   1343 use IO::Async::Loop;
  11         16941  
  11         257  
14 11     11   45 use List::Util qw(any);
  11         23  
  11         530  
15 11     11   56 use POSIX;
  11         13  
  11         109  
16 11     11   25036 use PPR;
  11         311257  
  11         375  
17 11     11   8624 use Path::Tiny;
  11         116278  
  11         634  
18 11     11   8601 use Storable;
  11         29722  
  11         526  
19 11     11   4673 use Time::Piece;
  11         83478  
  11         37  
20 11     11   4043 use URI::file;
  11         49426  
  11         44259  
21              
22             =head1 NAME
23              
24             PLS::Parser::Index
25              
26             =head1 DESCRIPTION
27              
28             This class caches and stores indexed data about the workspace.
29             It is used for quick searching of subroutines and packages by name.
30              
31             =cut
32              
33             sub new
34             {
35 86     86 0 511 my ($class, @args) = @_;
36              
37 86         173 state $self;
38 86 100       733 return $self if (ref $self eq 'PLS::Parser::Index');
39              
40 7         73 my %args = @args;
41             $self = bless {
42             workspace_folders => $args{workspace_folders},
43 7         337 subs => {},
44             packages => {},
45             subs_by_package => {},
46             files => {},
47             ignored_files => {},
48             ignore_files_mtimes => {}
49             }, $class;
50              
51 7         98 return $self;
52             } ## end sub new
53              
54             sub workspace_folders
55             {
56 36     36 0 112 my ($self) = @_;
57              
58 36         346 return $self->{workspace_folders};
59             }
60              
61             sub subs
62             {
63 28     28 0 35 my ($self) = @_;
64              
65 28         61 return $self->{subs};
66             }
67              
68             sub packages
69             {
70 4     4 0 30 my ($self) = @_;
71              
72 4         19 return $self->{packages};
73             }
74              
75             sub subs_by_package
76             {
77 28     28 0 38 my ($self) = @_;
78              
79 28         75 return $self->{subs_by_package};
80             }
81              
82             sub files
83             {
84 60     60 0 94 my ($self) = @_;
85              
86 60         250 return $self->{files};
87             }
88              
89             sub _index_file
90             {
91 0     0   0 my ($uri, $text) = @_;
92              
93 0 0       0 $text = PLS::Parser::Document->text_from_uri($uri) if (ref $text ne 'SCALAR');
94 0         0 my $line_offsets = PLS::Parser::Index->get_line_offsets($text);
95 0         0 my $packages = PLS::Parser::Index->get_packages($text, $uri, $line_offsets);
96 0         0 my $subroutines = PLS::Parser::Index->get_subroutines($text, $uri, $line_offsets);
97              
98 0         0 return $packages, $subroutines;
99             } ## end sub _index_file
100              
101             sub index_files
102             {
103 5     5 0 24 my ($self, @uris) = @_;
104              
105 5         7 state $function;
106              
107 5 50       25 if (ref $function ne 'IO::Async::Function')
108             {
109 5         183 $function = IO::Async::Function->new(code => \&_index_file);
110 5         967 IO::Async::Loop->new->add($function);
111             }
112              
113 5         47141 my $get_files_future;
114              
115 5 50       208 if (scalar @uris)
116             {
117 0         0 $get_files_future = Future->done(\@uris);
118             }
119             else
120             {
121 5         174 $get_files_future = $self->get_all_perl_files_async();
122             }
123              
124             return $get_files_future->then(
125             sub {
126 4     4   5986 my ($uris) = @_;
127              
128 4         56 my @futures;
129              
130 4         208 my %open_files = map { $_ => 1 } PLS::Parser::Document->open_files();
  4         56  
131 4         257 require PLS::Parser::Document;
132              
133 4         73 foreach my $uri (@{$uris})
  4         14  
134             {
135             # If the file is open, pass the current text to the function, since the other
136             # process will not have the most up-to-date document text.
137             # If the file is not open, allow the function to open the file and get the text.
138 4         132 my $version = PLS::Parser::Document::uri_version($uri);
139 4 50       71 my $text = length $version ? PLS::Parser::Document->text_from_uri($uri) : undef;
140              
141             push @futures, $function->call(args => [$uri, $text])->then(
142             sub {
143 4         244829 my ($packages, $subs) = @_;
144              
145 4         56 my $current_version = PLS::Parser::Document::uri_version($uri);
146 4 0 33     41 return if (length $version and length $current_version and $current_version < $version);
      33        
147              
148 4         123 my $file = URI->new($uri)->file;
149 4 50       1439 return if $self->is_ignored($file);
150              
151 4 50       61 $file = readlink $file if (-l $file);
152 4 50       63 return if $self->is_ignored($file);
153              
154 4         34 $self->cleanup_file($file);
155              
156 4         12 foreach my $ref (keys %{$packages})
  4         32  
157             {
158 4         12 push @{$self->packages->{$ref}}, @{$packages->{$ref}};
  4         26  
  4         2025  
159 4         11 push @{$self->files->{$file}{packages}}, $ref;
  4         13  
160             }
161              
162 4         14 foreach my $ref (keys %{$subs})
  4         21  
163             {
164 28         32 push @{$self->subs->{$ref}}, @{$subs->{$ref}};
  28         57  
  28         67  
165 28         35 push @{$self->files->{$file}{subs}}, $ref;
  28         39  
166              
167 28         36 foreach my $sub (@{$subs->{$ref}})
  28         44  
168             {
169 28 50       55 push @{$self->subs_by_package->{$sub->{package}}}, $ref if (length $sub->{package});
  28         50  
170             }
171              
172             } ## end foreach my $ref (keys %{$subs...})
173              
174 4         52 return Future->done($file);
175             }
176 4         54 );
177             } ## end foreach my $uri (@{$uris})
178              
179 4         5578 return Future->done(@futures);
180             }
181 5         11460 )->retain();
182             } ## end sub index_files
183              
184             sub get_all_perl_files_async
185             {
186 5     5 0 131 my ($self, @folders) = @_;
187              
188 5 50       184 @folders = @{$self->workspace_folders} unless (scalar @folders);
  5         200  
189 5 50       92 return Future->done([]) unless (scalar @folders);
190              
191 5         69 state $function;
192              
193 5 50       88 if (ref $function ne 'IO::Async::Function')
194             {
195 5         188 $function = IO::Async::Function->new(code => \&get_all_perl_files);
196              
197 5         758 IO::Async::Loop->new->add($function);
198             } ## end if (ref $function ne 'IO::Async::Function'...)
199              
200 5         36927 return $function->call(args => [$self, @folders]);
201             } ## end sub get_all_perl_files_async
202              
203             sub deindex_workspace
204             {
205 0     0 0 0 my ($self, $path) = @_;
206              
207 0         0 @{$self->workspace_folders} = grep { $_ ne $path } @{$self->workspace_folders};
  0         0  
  0         0  
  0         0  
208              
209 0         0 foreach my $file (keys %{$self->files})
  0         0  
210             {
211 0 0       0 next unless path($path)->subsumes($file);
212              
213 0         0 $self->cleanup_file($file);
214             } ## end foreach my $file (keys %{$self...})
215              
216 0         0 return;
217             } ## end sub deindex_workspace
218              
219             sub index_workspace
220             {
221 0     0 0 0 my ($self, $path) = @_;
222              
223 0         0 push @{$self->workspace_folders}, $path;
  0         0  
224              
225             $self->get_all_perl_files_async($path)->then(
226             sub {
227 0     0   0 my ($workspace_uris) = @_;
228              
229 0         0 return $self->index_files(@{$workspace_uris});
  0         0  
230             }
231 0     0   0 )->then(sub { Future->wait_all(@_) })->retain();
  0         0  
232              
233 0         0 return;
234             } ## end sub index_workspace
235              
236             sub cleanup_file
237             {
238 4     4 0 43 my ($self, $file) = @_;
239              
240 4 50       48 $self->files->{$file}{subs} = [] if (ref $self->files->{$file}{subs} ne 'ARRAY');
241 4 50       17 $self->files->{$file}{packages} = [] if (ref $self->files->{$file}{packages} ne 'ARRAY');
242              
243 4         10 foreach my $ref (@{$self->files->{$file}{subs}})
  4         10  
244             {
245 0         0 @{$self->subs->{$ref}} = grep { $_->{uri} ne URI::file->new($file)->as_string() } @{$self->subs->{$ref}};
  0         0  
  0         0  
  0         0  
246 0 0       0 delete $self->subs->{$ref} unless (scalar @{$self->subs->{$ref}});
  0         0  
247             }
248              
249 4         25 foreach my $ref (@{$self->files->{$file}{packages}})
  4         33  
250             {
251 0         0 my @old_packages = @{$self->packages->{$ref}};
  0         0  
252 0         0 @{$self->packages->{$ref}} = ();
  0         0  
253              
254 0         0 foreach my $package (@old_packages)
255             {
256 0 0       0 if ($package->{uri} eq URI::file->new($file)->as_string())
257             {
258 0         0 delete $self->subs_by_package->{$ref};
259             }
260             else
261             {
262 0         0 push @{$self->packages->{$ref}}, $package;
  0         0  
263             }
264             } ## end foreach my $package (@old_packages...)
265              
266 0 0       0 delete $self->packages->{$ref} unless (scalar @{$self->packages->{$ref}});
  0         0  
267             } ## end foreach my $ref (@{$self->files...})
268              
269 4         38 delete $self->files->{$file};
270              
271 4         24 return;
272             } ## end sub cleanup_file
273              
274             sub find_package_subroutine
275             {
276 0     0 0 0 my ($self, $package, $subroutine) = @_;
277              
278 0         0 my $locations = $self->packages->{$package};
279              
280 0 0       0 if (ref $locations ne 'ARRAY')
281             {
282 0         0 require PLS::Parser::Document;
283 0         0 my $external = PLS::Parser::Document->find_external_subroutine($package, $subroutine);
284 0 0       0 return [$external] if (ref $external eq 'HASH');
285 0         0 return [];
286             } ## end if (ref $locations ne ...)
287              
288 0         0 my @subroutines;
289              
290 0         0 foreach my $file (@{$locations})
  0         0  
291             {
292 0         0 push @subroutines, @{$self->find_subroutine($subroutine, $file->{uri})};
  0         0  
293             }
294              
295 0         0 return \@subroutines;
296             } ## end sub find_package_subroutine
297              
298             sub find_subroutine
299             {
300 0     0 0 0 my ($self, $subroutine, @uris) = @_;
301              
302 0         0 my $found = $self->subs->{$subroutine};
303 0 0       0 return [] unless (ref $found eq 'ARRAY');
304              
305 0         0 $found = Storable::dclone($found);
306 0         0 my %uris = map { $_ => 1 } @uris;
  0         0  
307 0 0       0 @{$found} = grep { $uris{$_->{uri}} } @{$found} if (scalar @uris);
  0         0  
  0         0  
  0         0  
308              
309 0         0 return $found;
310             } ## end sub find_subroutine
311              
312             sub find_package
313             {
314 0     0 0 0 my ($self, $package) = @_;
315              
316 0         0 my $found = $self->packages->{$package};
317              
318 0 0       0 if (ref $found ne 'ARRAY')
319             {
320 0         0 require PLS::Parser::Document;
321 0         0 my $external = PLS::Parser::Document->find_external_package($package);
322 0 0       0 return [$external] if (ref $external eq 'HASH');
323 0         0 return [];
324             } ## end if (ref $found ne 'ARRAY'...)
325              
326 0         0 return Storable::dclone($found);
327             } ## end sub find_package
328              
329             sub get_ignored_files
330             {
331 8     8 0 28 my ($self) = @_;
332              
333 8         26 foreach my $workspace_folder (@{$self->workspace_folders})
  8         61  
334             {
335 8         124 my $plsignore = File::Spec->catfile($workspace_folder, '.plsignore');
336 8 50 33     156 next if (not -f $plsignore or not -r $plsignore);
337              
338 0         0 my $mtime = stat($plsignore)->mtime;
339 0 0 0     0 next if (length $self->{ignore_files_mtimes}{$plsignore} and $self->{ignore_file_mtimes}{$plsignore} >= $mtime);
340              
341 0 0       0 open my $fh, '<', $plsignore or next;
342              
343 0         0 $self->{ignored_files}{$plsignore} = [];
344 0         0 $self->{ignore_file_mtimes}{$plsignore} = $mtime;
345              
346 0         0 while (my $line = <$fh>)
347             {
348 0         0 chomp $line;
349 0         0 push @{$self->{ignored_files}{$plsignore}}, glob File::Spec->catfile($workspace_folder, $line);
  0         0  
350             }
351              
352 0         0 @{$self->{ignored_files}{$plsignore}} = map { path($_)->realpath } @{$self->{ignored_files}{$plsignore}};
  0         0  
  0         0  
  0         0  
353             } ## end foreach my $workspace_folder...
354              
355 8         30 return [map { @{$self->{ignored_files}{$_}} } keys %{$self->{ignored_files}}];
  0         0  
  0         0  
  8         57  
356             } ## end sub get_ignored_files
357              
358             sub get_all_packages
359             {
360 0     0 0 0 my ($self) = @_;
361              
362 0 0       0 return [] if (ref $self->packages ne 'HASH');
363 0         0 return [keys %{$self->packages}];
  0         0  
364             } ## end sub get_all_packages
365              
366             sub get_all_fully_qualified_subroutines
367             {
368 0     0 0 0 my ($self) = @_;
369              
370 0 0       0 return [] if (ref $self->subs_by_package ne 'HASH');
371              
372 0         0 my @subroutines;
373              
374 0         0 foreach my $package (keys %{$self->subs_by_package})
  0         0  
375             {
376 0         0 foreach my $subroutine (@{$self->subs_by_package->{$package}})
  0         0  
377             {
378 0         0 push @subroutines, "${package}::${subroutine}";
379             }
380             } ## end foreach my $package (keys %...)
381              
382 0         0 return \@subroutines;
383             } ## end sub get_all_fully_qualified_subroutines
384              
385             sub is_ignored
386             {
387 8     8 0 55 my ($self, $file) = @_;
388              
389 8         21 my @ignore_files = @{$self->get_ignored_files()};
  8         62  
390 8 50       45 return unless (scalar @ignore_files);
391              
392 0           my $real_path = path($file)->realpath;
393              
394 0 0   0     return 1 if any { $_ eq $real_path } @ignore_files;
  0            
395 0 0   0     return 1 if any { $_->subsumes($real_path) } @ignore_files;
  0            
396              
397 0           return;
398             } ## end sub is_ignored
399              
400             sub get_all_perl_files
401             {
402 0     0 0   my ($self, @folders) = @_;
403              
404 0 0         @folders = @{$self->workspace_folders} unless (scalar @folders);
  0            
405 0 0         return [] unless (scalar @folders);
406              
407 0           my @perl_files;
408              
409             File::Find::find(
410             {
411             preprocess => sub {
412 0 0   0     return () if $self->is_ignored($File::Find::dir);
413 0           return grep { not $self->is_ignored($_) } @_;
  0            
414             },
415             wanted => sub {
416 0 0   0     return unless $self->is_perl_file($File::Find::name);
417 0           my @pieces = File::Spec->splitdir($File::Find::name);
418              
419             # exclude hidden files and files in hidden directories
420 0 0         return if any { /^\./ } @pieces;
  0            
421              
422 0           push @perl_files, $File::Find::name;
423             }
424             },
425             @folders
426 0           );
427              
428 0           return [map { URI::file->new($_)->as_string } @perl_files];
  0            
429             } ## end sub get_all_perl_files
430              
431             sub is_perl_file
432             {
433 0     0 0   my ($class, $file) = @_;
434              
435 0 0         return if -l $file;
436 0 0         return unless -f $file;
437 0 0   0     return if any { /^\.pls-tmp/ } grep { length } File::Spec->splitdir($file);
  0            
  0            
438 0 0         return if $file =~ /\.t$/;
439              
440 0 0         return 1 if $file =~ /\.p[lm]$/;
441 0 0         open my $fh, '<', $file or return;
442 0           my $first_line = <$fh>;
443 0           close $fh;
444 0 0 0       return 1 if (length $first_line and $first_line =~ /^\s*#!.*perl$/);
445 0           return;
446             } ## end sub is_perl_file
447              
448             sub log
449             {
450 0     0 0   my (undef, $message) = @_;
451              
452 0           my $time = Time::Piece->new;
453 0           $time = $time->ymd . ' ' . $time->hms;
454 0           print {\*STDERR} "[$time] $message\n";
  0            
455              
456 0           return;
457             } ## end sub log
458              
459             sub get_line_offsets
460             {
461 0     0 0   my ($class, $text) = @_;
462              
463 0           my @line_offsets = (0);
464              
465 0           while ($$text =~ /\r?\n/g)
466             {
467 0           push @line_offsets, pos($$text);
468             }
469              
470 0           return \@line_offsets;
471             } ## end sub get_line_offsets
472              
473             sub get_line_by_offset
474             {
475 0     0 0   my ($class, $line_offsets, $offset) = @_;
476              
477 0           for (my $i = 0 ; $i <= $#{$line_offsets} ; $i++)
  0            
478             {
479 0           my $current_offset = $line_offsets->[$i];
480 0 0         my $next_offset = $i + 1 <= $#{$line_offsets} ? $line_offsets->[$i + 1] : undef;
  0            
481              
482 0 0 0       if ($current_offset <= $offset and (not defined $next_offset or $next_offset > $offset))
      0        
483             {
484 0           return $i;
485             }
486             } ## end for (my $i = 0 ; $i <= ...)
487              
488 0           return $#{$line_offsets};
  0            
489             } ## end sub get_line_by_offset
490              
491             sub get_packages
492             {
493 0     0 0   my ($class, $text, $uri, $line_offsets) = @_;
494              
495 0           state $rx = qr/((?&PerlPackageDeclaration))$PPR::GRAMMAR/x;
496 0           my %packages;
497              
498 0           my $file = URI->new($uri)->file;
499 0 0         $file = readlink $file if (-l $file);
500 0           $uri = URI::file->new($file)->as_string();
501              
502 0           while ($$text =~ /$rx/g)
503             {
504 0           my $name = $1;
505              
506 0           my $end = pos($$text);
507 0           my $start = $end - length $name;
508 0           my $start_line = $class->get_line_by_offset($line_offsets, $start);
509 0           $start -= $line_offsets->[$start_line];
510 0           my $end_line = $class->get_line_by_offset($line_offsets, $end);
511 0           $end -= $line_offsets->[$end_line];
512              
513 0           ($name) = $name =~ /package\s*(\S+)/;
514 0           $name =~ s/;$//;
515              
516 0           push @{$packages{$name}},
  0            
517             {
518             uri => $uri,
519             range => {
520             start => {
521             line => $start_line,
522             character => $start
523             },
524             end => {
525             line => $end_line,
526             character => $end
527             }
528             }
529             };
530             } ## end while ($$text =~ /$rx/g)
531              
532 0           return \%packages;
533             } ## end sub get_packages
534              
535             sub get_subroutines
536             {
537 0     0 0   my ($class, $text, $uri, $line_offsets) = @_;
538              
539 0           my $file = URI->new($uri)->file;
540 0 0         $file = readlink $file if (-l $file);
541 0           $uri = URI::file->new($file)->as_string();
542              
543             # Stolen mostly from PPR definition for PerlSubroutineDeclaration
544 0           state $sub_rx = qr/
545             (?<full>
546             (?<declaration>(?>
547             (?: (?> my | our | state ) \b (?>(?&PerlOWS)) )?+
548             sub \b (?>(?&PerlOWS))
549             (?<name>(?>(?&PerlOldQualifiedIdentifier))) (?&PerlOWS)
550             |
551             (?<name>AUTOLOAD) (?&PerlOWS)
552             |
553             (?<name>DESTROY) (?&PerlOWS)
554             ))
555             (?:
556             # Perl pre 5.028
557             (?:
558             (?>
559             (?<params>(?<label>(?&PerlParenthesesList))) # Parameter list
560             |
561             \( [^)]*+ \) # Prototype (
562             )
563             (?&PerlOWS)
564             )?+
565             (?: (?>(?&PerlAttributes)) (?&PerlOWS) )?+
566             |
567             # Perl post 5.028
568             (?: (?>(?&PerlAttributes)) (?&PerlOWS) )?+
569             (?<params>(?<label>(?: (?>(?&PerlParenthesesList)) (?&PerlOWS) )?+)) # Parameter list
570             )
571             (?> ; | \{
572             (?&PerlOWS)
573             (?<label>(?<params>(?&PerlVariableDeclaration))(?&PerlOWS)=(?&PerlOWS)\@_;?)?
574             (?&PerlOWS)
575             (?>(?&PerlStatementSequence))
576             \} )
577             )
578             $PPR::GRAMMAR/x;
579              
580 0           state $var_rx = qr/((?&PerlVariable)|undef)$PPR::GRAMMAR/;
581 0           state $package_rx = qr/((?&PerlPackageDeclaration))$PPR::GRAMMAR/;
582              
583 0           my %subroutines;
584              
585 0           while ($$text =~ /$sub_rx/g)
586             {
587 0           my $end = pos($$text);
588 0           my $start = $end - length $+{full};
589 0           $end = $start + length $+{declaration};
590              
591 0           my $start_line = $class->get_line_by_offset($line_offsets, $start);
592 0           $start -= $line_offsets->[$start_line];
593 0           my $end_line = $class->get_line_by_offset($line_offsets, $end);
594 0           $end -= $line_offsets->[$end_line];
595              
596 0           my $signature = $+{label};
597 0           my @parameters;
598              
599 0 0         if (length $+{params})
600             {
601 0           my $parameters = $+{params};
602 0           while ($parameters =~ /$var_rx/g)
603             {
604 0           push @parameters, {label => $1};
605             }
606             } ## end if (length $+{params})
607              
608 0           my $name = $+{name};
609              
610             # Look for package declaration anywhere from the start of the document
611             # to the subroutine declaration.
612 0           my $package;
613              
614 0 0         if (substr($$text, 0, pos($$text)) =~ /$package_rx/)
615             {
616 0           ($package) = $1 =~ /^package\s+(.+)\s*;\s*$/;
617             }
618              
619 0           push @{$subroutines{$name}},
  0            
620             {
621             uri => $uri,
622             range => {
623             start => {
624             line => $start_line,
625             character => $start
626             },
627             end => {
628             line => $end_line,
629             character => $end
630             }
631             },
632             signature => {label => $signature, parameters => \@parameters},
633             'package' => $package,
634             kind => 3
635             };
636             } ## end while ($$text =~ /$sub_rx/g...)
637              
638 0           state $block_rx = qr/use\h+constant(?&PerlOWS)((?&PerlBlock))$PPR::GRAMMAR/;
639 0           state $bareword_rx = qr/((?&PerlBareword))(?&PerlOWS)(?&PerlComma)$PPR::GRAMMAR/;
640 0           state $one_constant_rx = qr/use\h+constant\h+((?&PerlBareword))(?&PerlOWS)(?&PerlComma)$PPR::GRAMMAR/;
641              
642 0           while ($$text =~ /$block_rx/g)
643             {
644 0           my $block = $1;
645 0           my $block_end = $+[1];
646 0           my $block_start = $-[1];
647              
648             # Look for package declaration anywhere from the start of the document
649             # to the constant declaration
650 0           my $package;
651              
652 0 0         if (substr($$text, 0, pos($$text)) =~ /$package_rx/)
653             {
654 0           ($package) = $1 =~ /^package\s+(.+)\s*;\s*$/;
655             }
656              
657 0           while ($block =~ /$bareword_rx/g)
658             {
659 0           my $bareword = $1;
660 0           my $bareword_end = $+[1];
661 0           my $bareword_start = $-[1];
662              
663 0           $bareword_start += $block_start;
664 0           $bareword_end += $block_start;
665              
666 0           my $start_line = $class->get_line_by_offset($line_offsets, $bareword_start);
667 0           $bareword_start -= $line_offsets->[$start_line];
668 0           my $end_line = $class->get_line_by_offset($line_offsets, $bareword_end);
669 0           $bareword_end -= $line_offsets->[$end_line];
670              
671 0           push @{$subroutines{$bareword}}, {
  0            
672             uri => $uri,
673             range => {
674             start => {
675             line => $start_line,
676             character => $bareword_start
677             },
678             end => {
679             line => $end_line,
680             character => $bareword_end
681             }
682             },
683             'package' => $package,
684             kind => 21 # constant kind
685             };
686             } ## end while ($block =~ /$bareword_rx/g...)
687             } ## end while ($$text =~ /$block_rx/g...)
688              
689 0           while ($$text =~ /$one_constant_rx/g)
690             {
691 0           my $bareword = $1;
692 0           my $end = $+[1];
693 0           my $start = $-[1];
694              
695 0           my $start_line = $class->get_line_by_offset($line_offsets, $start);
696 0           $start -= $line_offsets->[$start_line];
697 0           my $end_line = $class->get_line_by_offset($line_offsets, $end);
698 0           $end -= $line_offsets->[$end_line];
699              
700             # Look for package declaration anywhere from the start of the document
701             # to the constant declaration
702 0           my $package;
703              
704 0 0         if (substr($$text, 0, pos($$text)) =~ /$package_rx/)
705             {
706 0           ($package) = $1 =~ /^package\s+(.+)\s*;\s*$/;
707             }
708              
709 0           push @{$subroutines{$bareword}}, {
  0            
710             uri => $uri,
711             range => {
712             start => {
713             line => $start_line,
714             character => $start
715             },
716             end => {
717             line => $end_line,
718             character => $end
719             }
720             },
721             'package' => $package,
722             kind => 21 # constant kind
723             };
724             } ## end while ($$text =~ /$one_constant_rx/g...)
725              
726 0           return \%subroutines;
727             } ## end sub get_subroutines
728              
729             1;