File Coverage

blib/lib/PPIx/EditorTools.pm
Criterion Covered Total %
statement 101 139 72.6
branch 47 74 63.5
condition 52 93 55.9
subroutine 14 14 100.0
pod 0 6 0.0
total 214 326 65.6


line stmt bran cond sub pod time code
1             package PPIx::EditorTools;
2             our $AUTHORITY = 'cpan:YANICK';
3             # ABSTRACT: Utility methods and base class for manipulating Perl via PPI
4             $PPIx::EditorTools::VERSION = '0.20';
5 11     11   178978 use 5.008;
  11         39  
6 11     11   53 use strict;
  11         20  
  11         191  
7 11     11   44 use warnings;
  11         18  
  11         253  
8 11     11   53 use Carp;
  11         20  
  11         677  
9 11         69 use Class::XSAccessor 1.02 constructor => 'new', accessors => {
10             'code' => 'code',
11             'ppi' => 'ppi',
12 11     11   2696 };
  11         16904  
13              
14 11     11   2965 use PPI 1.203;
  11         83579  
  11         213  
15 11     11   3140 use PPIx::EditorTools::ReturnObject;
  11         23  
  11         12960  
16              
17              
18             # Used by all the PPIx::EditorTools::* modules
19             # Checks for either PPI::Document or take the code as a string and
20             # creates the ppi document
21              
22             sub process_doc {
23 52     52 0 9040 my ( $self, %args ) = @_;
24              
25 52 50       174 $self->ppi( $args{ppi} ) if defined $args{ppi};
26 52 100 66     329 return 1 if $self->ppi && $self->ppi->isa('PPI::Document');
27              
28             # TODO: inefficient to pass around full code/ppi
29 41 100       177 $self->code( $args{code} ) if $args{code};
30 41         86 my $code = $self->code;
31 41         235 $self->ppi( PPI::Document->new( \$code ) );
32 41 100 66     275299 return 1 if $self->ppi && $self->ppi->isa('PPI::Document');
33              
34 1         25 croak "arguments ppi or code required";
35 0         0 return;
36             }
37              
38              
39              
40              
41              
42             #####################################################################
43             # Assorted Search Functions
44              
45             sub find_unmatched_brace {
46 59 50   59 0 710 $_[1]->isa('PPI::Statement::UnmatchedBrace') and return 1;
47 58 100       153 $_[1]->isa('PPI::Structure') or return '';
48 3 100 66     17 $_[1]->start and $_[1]->finish and return '';
49 2         26 return 1;
50             }
51              
52             # scans a document for variable declarations and
53             # sorts them into three categories:
54             # lexical (my)
55             # our (our, doh)
56             # dynamic (local)
57             # package (use vars)
58             # Returns a hash reference containing the three category names
59             # each pointing at a hash which contains '$variablename' => locations.
60             # locations is an array reference containing one or more PPI-style
61             # locations. Example:
62             # {
63             # lexical => {
64             # '$foo' => [ [ 2, 3, 3], [ 6, 7, 7 ] ],
65             # },
66             # ...
67             # }
68             # Thus, there are two places where a "my $foo" was declared. On line 2 col 3
69             # and line 6 col 7.
70             sub get_all_variable_declarations {
71 3     3 0 11628 my $document = shift;
72              
73             my $declarations = $document->find(
74             sub {
75 55 100 66 55   946 return 0
      100        
76             unless $_[1]->isa('PPI::Statement::Variable')
77             or $_[1]->isa('PPI::Statement::Include')
78             or $_[1]->isa('PPI::Statement::Compound');
79 3         9 return 1;
80             },
81 3         40 );
82              
83 2         35 my %our;
84             my %lexical;
85 2         0 my %dynamic;
86 2         0 my %package;
87 2         5 foreach my $decl (@$declarations) {
88 3 50 66     61 if ( $decl->isa('PPI::Statement::Variable') ) {
    50 33        
    100 33        
      66        
89 0         0 my $type = $decl->type();
90 0         0 my @vars = $decl->variables;
91 0         0 my $location = $decl->location;
92              
93 0         0 my $target_type;
94              
95 0 0       0 if ( $type eq 'my' ) {
    0          
    0          
96 0         0 $target_type = \%lexical;
97             } elsif ( $type eq 'our' ) {
98 0         0 $target_type = \%our;
99             } elsif ( $type eq 'local' ) {
100 0         0 $target_type = \%dynamic;
101             }
102              
103 0         0 foreach my $var (@vars) {
104 0   0     0 $target_type->{$var} ||= [];
105 0         0 push @{ $target_type->{$var} }, $location;
  0         0  
106             }
107             }
108              
109             # find use vars...
110             elsif ( $decl->isa('PPI::Statement::Include')
111             and $decl->module eq 'vars'
112             and $decl->type eq 'use' )
113             {
114              
115             # do it the low-tech way
116 0         0 my $string = $decl->content();
117 0         0 my $location = $decl->location;
118              
119 0         0 my @vars = $string =~ /([\%\@\$][\w_:]+)/g;
120 0         0 foreach my $var (@vars) {
121 0   0     0 $package{$var} ||= [];
122 0         0 push @{ $package{$var} }, $location;
  0         0  
123             }
124              
125             }
126              
127             # find for/foreach loop variables
128             elsif ( $decl->isa('PPI::Statement::Compound')
129             && ( $decl->type eq 'for' or $decl->type eq 'foreach' ) )
130             {
131 2         214 my @elems = $decl->elements;
132              
133 2 50       19 next if scalar(@elems) < 5;
134 2         8 my $location = $decl->location;
135 2         1623 my $type = $elems[2]->content();
136 2 50 33     19 if ( $elems[4]->isa('PPI::Token::Symbol')
      33        
137             && ( $type eq 'my' || $type eq 'our' ) )
138             {
139 2         3 my $target_type;
140              
141             # Only my and our are valid for loop variable
142 2 50       7 if ( $type eq 'my' ) {
    0          
143 2         5 $target_type = \%lexical;
144             } elsif ( $type eq 'our' ) {
145 0         0 $target_type = \%our;
146             }
147              
148 2         7 my $var = $elems[4]->content();
149 2   50     16 $target_type->{$var} ||= [];
150 2         4 push @{ $target_type->{$var} }, $location;
  2         9  
151             }
152             }
153             } # end foreach declaration
154              
155             return (
156 2         23 { our => \%our,
157             lexical => \%lexical,
158             dynamic => \%dynamic,
159             package => \%package
160             }
161             );
162             }
163              
164              
165              
166              
167              
168             #####################################################################
169             # Stuff that should be in PPI itself
170              
171             sub element_depth {
172 1     1 0 47 my $cursor = shift;
173 1         3 my $depth = 0;
174 1         19 while ( $cursor = $cursor->parent ) {
175 0         0 $depth += 1;
176             }
177 0         0 return $depth;
178             }
179              
180             # TODO: PPIx::IndexOffsets or something similar might help.
181             # TODO: See the 71... tests. If we don#t flush locations there, this breaks.
182             sub find_token_at_location {
183 17     17 0 73 my $document = shift;
184 17         24 my $location = shift;
185              
186 17 50 66     164 if ( not defined $document
      66        
      33        
187             or not $document->isa('PPI::Document')
188             or not defined $location
189             or not ref($location) eq 'ARRAY' )
190             {
191 1         12 require Carp;
192 1         15 Carp::croak("find_token_at_location() requires a PPI::Document and a PPI-style location as arguments");
193             }
194              
195 16         59 $document->index_locations();
196              
197 16         30424 foreach my $token ( $document->tokens ) {
198 656         4777 my $loc = $token->location;
199 656 100 100     6423 if ( $loc->[0] > $location->[0]
      66        
200             or ( $loc->[0] == $location->[0] and $loc->[1] > $location->[1] ) )
201             {
202 16         51 $document->flush_locations();
203 16         5088 return $token->previous_token();
204             }
205             }
206 0         0 $document->flush_locations();
207 0         0 return ();
208             }
209              
210             # given either a PPI::Token::Symbol (i.e. a variable)
211             # or a PPI::Token which contains something that looks like
212             # a variable (quoted vars, interpolated vars in regexes...)
213             # find where that variable has been declared lexically.
214             # Doesn't find stuff like "use vars...".
215             sub find_variable_declaration {
216 10     10 0 18 my $cursor = shift;
217             return ()
218 10 50 33     70 if not $cursor
219             or not $cursor->isa("PPI::Token");
220 10         27 my ( $varname, $token_str );
221 10 50       32 if ( $cursor->isa("PPI::Token::Symbol") ) {
222 10         33 $varname = $cursor->symbol;
223 10         650 $token_str = $cursor->content;
224             } else {
225 0         0 my $content = $cursor->content;
226 0 0       0 if ( $content =~ /((?:\$#?|[@%*])[\w:\']+)/ ) {
227 0         0 $varname = $1;
228 0         0 $token_str = $1;
229             }
230             }
231             return ()
232 10 50       69 if not defined $varname;
233              
234 10         22 $varname =~ s/^\$\#/@/;
235              
236 10         32 my $document = $cursor->top();
237 10         141 my $declaration;
238             my $prev_cursor;
239              
240             # This finds variable declarations if you're above it
241 10 100       30 if ( $cursor->parent->isa('PPI::Statement::Variable') ) {
242 6         39 return $cursor->parent;
243             }
244              
245             # This finds variable declarations if you're above it and it has the form my ($foo , $bar);
246 4 50 33     31 if ( $cursor->parent->isa('PPI::Statement::Expression')
247             && $cursor->parent->parent->parent->isa('PPI::Statement::Variable') )
248             {
249 0         0 return $cursor->parent->parent->parent;
250             }
251              
252 4         24 while (1) {
253 12         90 $prev_cursor = $cursor;
254 12         47 $cursor = $cursor->parent;
255 12 100 100     71 if ( $cursor->isa("PPI::Structure::Block") or $cursor == $document ) {
    100 66        
256 6         29 my @elems = $cursor->elements;
257 6         65 foreach my $elem (@elems) {
258              
259             # Stop scanning this scope if we're at the branch we're coming
260             # from. This is to ignore declarations later in the block.
261 45 100       106 last if $elem == $prev_cursor;
262              
263 43 100 100     314 if ( $elem->isa("PPI::Statement::Variable")
    50 66        
      33        
264 6         270 and grep { $_ eq $varname } $elem->variables )
265             {
266 4         8 $declaration = $elem;
267 4         6 last;
268             }
269              
270             # find use vars ...
271             elsif ( $elem->isa("PPI::Statement::Include")
272             and $elem->module eq 'vars'
273             and $elem->type eq 'use' )
274             {
275              
276             # do it the low-tech way
277 0         0 my $string = $elem->content();
278 0         0 my @vars = $string =~ /([\%\@\$][\w_:]+)/g;
279 0 0       0 if ( grep { $varname eq $_ } @vars ) {
  0         0  
280 0         0 $declaration = $elem;
281 0         0 last;
282             }
283             }
284              
285             }
286 6 100 66     41 last if $declaration or $cursor == $document;
287             }
288              
289             # this is for "foreach my $i ..."
290             elsif ( $cursor->isa("PPI::Statement::Compound")
291             and $cursor->type() =~ /^for/ )
292             {
293 2         112 my @elems = $cursor->elements;
294 2         17 foreach my $elem (@elems) {
295              
296             # Stop scanning this scope if we're at the branch we're coming
297             # from. This is to ignore declarations later in the block.
298 18 100       256 last if $elem == $prev_cursor;
299              
300 16 100 100     86 if ( $elem->isa("PPI::Token::Word")
301             and $elem->content() =~ /^(?:my|our)$/ )
302             {
303 2         16 my $nelem = $elem->snext_sibling();
304 2 50 33     61 if ( defined $nelem
      33        
      33        
305             and $nelem->isa("PPI::Token::Symbol")
306             and $nelem->symbol() eq $varname || $nelem->content() eq $token_str )
307             {
308 0         0 $declaration = $nelem;
309 0         0 last;
310             }
311             }
312             }
313 2 50 33     13 last if $declaration or $cursor == $document;
314             }
315             } # end while not top level
316              
317 4         13 return $declaration;
318             }
319              
320             1;
321              
322             =pod
323              
324             =encoding UTF-8
325              
326             =head1 NAME
327              
328             PPIx::EditorTools - Utility methods and base class for manipulating Perl via PPI
329              
330             =head1 VERSION
331              
332             version 0.20
333              
334             =head1 SYNOPSIS
335              
336             See PPIx::EditorTools::*
337              
338             =head1 DESCRIPTION
339              
340             Base class and utility methods for manipulating Perl via PPI. Pulled out from
341             the C<Padre::Task::PPI> code.
342              
343             =head1 METHODS
344              
345             =over 4
346              
347             =item new()
348              
349             Constructor. Generally shouldn't be called with any arguments.
350              
351             =back
352              
353             =head1 SEE ALSO
354              
355             C<PPIx::EditorTools::*>, L<Padre>, L<App::EditorTools>, L<Padre>, and L<PPI>.
356              
357             =head1 AUTHORS
358              
359             =over 4
360              
361             =item *
362              
363             Steffen Mueller C<smueller@cpan.org>
364              
365             =item *
366              
367             Mark Grimes C<mgrimes@cpan.org>
368              
369             =item *
370              
371             Ahmad M. Zawawi <ahmad.zawawi@gmail.com>
372              
373             =item *
374              
375             Gabor Szabo <gabor@szabgab.com>
376              
377             =item *
378              
379             Yanick Champoux <yanick@cpan.org>
380              
381             =back
382              
383             =head1 COPYRIGHT AND LICENSE
384              
385             This software is copyright (c) 2017, 2014, 2012 by The Padre development team as listed in Padre.pm..
386              
387             This is free software; you can redistribute it and/or modify it under
388             the same terms as the Perl 5 programming language system itself.
389              
390             =cut
391              
392             __END__
393              
394              
395             # Copyright 2008-2009 The Padre development team as listed in Padre.pm.
396             # LICENSE
397             # This program is free software; you can redistribute it and/or
398             # modify it under the same terms as Perl 5 itself.