File Coverage

blib/lib/PPIx/EditorTools.pm
Criterion Covered Total %
statement 101 141 71.6
branch 47 74 63.5
condition 50 93 53.7
subroutine 14 14 100.0
pod 0 6 0.0
total 212 328 64.6


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