File Coverage

blib/lib/PLS/Parser/Index.pm
Criterion Covered Total %
statement 137 380 36.0
branch 16 106 15.0
condition 3 21 14.2
subroutine 27 49 55.1
pod 0 25 0.0
total 183 581 31.5


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