File Coverage

blib/lib/PPIx/Utils/Traversal.pm
Criterion Covered Total %
statement 160 183 87.4
branch 67 106 63.2
condition 37 69 53.6
subroutine 18 20 90.0
pod 7 7 100.0
total 289 385 75.0


line stmt bran cond sub pod time code
1             package PPIx::Utils::Traversal;
2              
3 3     3   191597 use strict;
  3         18  
  3         76  
4 3     3   13 use warnings;
  3         4  
  3         79  
5 3     3   12 use Exporter 'import';
  3         7  
  3         73  
6 3     3   13 use PPI::Token::Quote::Single;
  3         6  
  3         104  
7 3     3   13 use PPI::Document::Fragment;
  3         4  
  3         78  
8 3     3   13 use Scalar::Util 'refaddr';
  3         4  
  3         155  
9              
10 3     3   1075 use PPIx::Utils::Language qw(precedence_of);
  3         4  
  3         162  
11 3         5124 use PPIx::Utils::_Common qw(
12             is_ppi_expression_or_generic_statement
13             is_ppi_simple_statement
14 3     3   988 );
  3         6  
15              
16             our $VERSION = '0.003';
17              
18             our @EXPORT_OK = qw(
19             first_arg parse_arg_list split_nodes_on_comma
20             get_next_element_in_same_simple_statement
21             get_previous_module_used_on_same_line
22             get_constant_name_elements_from_declaring_statement
23             split_ppi_node_by_namespace
24             );
25              
26             our %EXPORT_TAGS = (all => [@EXPORT_OK]);
27              
28             # From Perl::Critic::Utils
29             my $MIN_PRECEDENCE_TO_TERMINATE_PARENLESS_ARG_LIST =
30             precedence_of( 'not' );
31              
32             sub first_arg {
33 7     7 1 6181 my $elem = shift;
34 7         19 my $sib = $elem->snext_sibling();
35 7 50       156 return undef if !$sib;
36              
37 7 100       23 if ( $sib->isa('PPI::Structure::List') ) {
38              
39 2         18 my $expr = $sib->schild(0);
40 2 100       25 return undef if !$expr;
41 1 50       6 return $expr->isa('PPI::Statement') ? $expr->schild(0) : $expr;
42             }
43              
44 5         10 return $sib;
45             }
46              
47             sub parse_arg_list {
48 11     11 1 11240 my $elem = shift;
49 11         31 my $sib = $elem->snext_sibling();
50 11 50       264 return() if !$sib;
51              
52 11 100       42 if ( $sib->isa('PPI::Structure::List') ) {
53              
54             #Pull siblings from list
55 3         29 my @list_contents = $sib->schildren();
56 3 50       33 return() if not @list_contents;
57              
58 3         6 my @list_expressions;
59 3         4 foreach my $item (@list_contents) {
60 3 50       11 if (
61             is_ppi_expression_or_generic_statement($item)
62             ) {
63 3         12 push
64             @list_expressions,
65             split_nodes_on_comma( $item->schildren() );
66             }
67             else {
68 0         0 push @list_expressions, $item;
69             }
70             }
71              
72 3         9 return @list_expressions;
73             }
74             else {
75              
76             #Gather up remaining nodes in the statement
77 8         11 my $iter = $elem;
78 8         12 my @arg_list = ();
79              
80 8         13 while ($iter = $iter->snext_sibling() ) {
81 19 100 66     380 last if $iter->isa('PPI::Token::Structure') and $iter eq ';';
82 11 50 66     31 last if $iter->isa('PPI::Token::Operator')
83             and $MIN_PRECEDENCE_TO_TERMINATE_PARENLESS_ARG_LIST <=
84             precedence_of( $iter );
85 11         42 push @arg_list, $iter;
86             }
87 8         112 return split_nodes_on_comma( @arg_list );
88             }
89             }
90              
91             sub split_nodes_on_comma {
92 11     11 1 56 my @nodes = @_;
93              
94 11         14 my $i = 0;
95 11         14 my @node_stacks;
96 11         19 for my $node (@nodes) {
97 26 100 66     125 if (
    100 66        
98             $node->isa('PPI::Token::Operator')
99             and ($node eq ',' or $node eq '=>')
100             ) {
101 8 50       124 if (@node_stacks) {
102 8         13 $i++; #Move forward to next 'node stack'
103             }
104 8         14 next;
105             } elsif ( $node->isa('PPI::Token::QuoteLike::Words' )) {
106 7         13 my $section = $node->{sections}->[0];
107 7         13 my @words = split ' ', substr $node->content, $section->{position}, $section->{size};
108 7         49 my $loc = $node->location;
109 7         5002 for my $word (@words) {
110 10         36 my $token = PPI::Token::Quote::Single->new(q{'} . $word . q{'});
111 10         128 $token->{_location} = $loc;
112 10         12 push @{ $node_stacks[$i++] }, $token;
  10         27  
113             }
114 7         14 next;
115             }
116 11         12 push @{ $node_stacks[$i] }, $node;
  11         26  
117             }
118 11         36 return @node_stacks;
119             }
120              
121             # From Perl::Critic::Utils::PPI
122             sub get_next_element_in_same_simple_statement {
123 0 0   0 1 0 my $element = shift or return undef;
124              
125 0   0     0 while ( $element and (
      0        
126             not is_ppi_simple_statement( $element )
127             or $element->parent()
128             and $element->parent()->isa( 'PPI::Structure::List' ) ) ) {
129 0         0 my $next;
130 0 0       0 $next = $element->snext_sibling() and return $next;
131 0         0 $element = $element->parent();
132             }
133 0         0 return undef;
134              
135             }
136              
137             sub get_previous_module_used_on_same_line {
138 0 0   0 1 0 my $element = shift or return undef;
139              
140 0 0       0 my ( $line ) = @{ $element->location() || []};
  0         0  
141              
142 0         0 while (not is_ppi_simple_statement( $element )) {
143 0 0       0 $element = $element->parent() or return undef;
144             }
145              
146 0         0 while ( $element = $element->sprevious_sibling() ) {
147 0 0       0 ( @{ $element->location() || []} )[0] == $line or return undef;
  0 0       0  
148 0 0       0 $element->isa( 'PPI::Statement::Include' )
149             and return $element->schild( 1 );
150             }
151              
152 0         0 return undef;
153             }
154             # End from Perl::Critic::Utils
155              
156             # From PPIx::Utilities::Statement
157             my %IS_COMMA = ( q[,] => 1, q[=>] => 1 );
158              
159             sub get_constant_name_elements_from_declaring_statement {
160 4     4 1 12101 my ($element) = @_;
161              
162 4 50       15 return() if not $element;
163 4 50       14 return() if not $element->isa('PPI::Statement');
164              
165 4 50 0     10 if ( $element->isa('PPI::Statement::Include') ) {
    0          
166 4         5 my $pragma;
167 4 50 33     12 if ( $pragma = $element->pragma() and $pragma eq 'constant' ) {
168 4         138 return _get_constant_names_from_constant_pragma($element);
169             }
170             } elsif ( not $element->specialized() and $element->schildren() > 2 ) {
171 0         0 my $supposed_constant_function = $element->schild(0)->content();
172 0         0 my $declaring_scope = $element->schild(1)->content();
173              
174 0 0 0     0 if (
      0        
      0        
175             (
176             $supposed_constant_function eq 'const'
177             or $supposed_constant_function =~ m< \A Readonly \b >x
178             )
179             and ($declaring_scope eq 'our' or $declaring_scope eq 'my')
180             ) {
181 0         0 return ($element->schild(2));
182             }
183             }
184              
185 0         0 return();
186             }
187              
188             sub _get_constant_names_from_constant_pragma {
189 4     4   9 my ($include) = @_;
190              
191 4 50       11 my @arguments = $include->arguments() or return();
192              
193 4         127 my $follower = $arguments[0];
194 4 50       8 return() if not defined $follower;
195              
196 4 100 66     16 if ($follower->isa('PPI::Token::Operator') && $follower->content eq '+') {
197 1         7 $follower = $arguments[1];
198 1 50       4 return() if not defined $follower;
199             }
200              
201             # We test for a 'PPI::Structure::Block' in the following because some
202             # versions of PPI parse the last element of 'use constant { ONE => 1, TWO
203             # => 2 }' as a block rather than a constructor. As of PPI 1.206, PPI
204             # handles the above correctly, but still blows it on 'use constant 1.16 {
205             # ONE => 1, TWO => 2 }'.
206 4 100 66     18 if (
207             $follower->isa( 'PPI::Structure::Constructor' )
208             or $follower->isa( 'PPI::Structure::Block' )
209             ) {
210 3 50       15 my $statement = $follower->schild( 0 ) or return();
211 3 50       154 $statement->isa( 'PPI::Statement' ) or return();
212              
213 3         4 my @elements;
214 3         5 my $inx = 0;
215 3         9 foreach my $child ( $statement->schildren() ) {
216 17 100       82 if (not $inx % 2) {
217 10   100     11 push @{ $elements[ $inx ] ||= [] }, $child;
  10         25  
218             }
219              
220 17 100       30 if ( $IS_COMMA{ $child->content() } ) {
221 7         23 $inx++;
222             }
223             }
224              
225             return map
226             {
227 3         14 (
228             $_
229 7 100 33     22 and @{$_} == 2
230             and '=>' eq $_->[1]->content()
231             and $_->[0]->isa( 'PPI::Token::Word' )
232             )
233             ? $_->[0]
234             : ()
235             }
236             @elements;
237             } else {
238 1         4 return ($follower);
239             }
240              
241 0         0 return ($follower);
242             }
243             # End from PPIx::Utilities::Statement
244              
245             # From PPIx::Utilities::Node
246             sub split_ppi_node_by_namespace {
247 10     10 1 104368 my ($node) = @_;
248              
249             # Ensure we don't screw up the original.
250 10         37 $node = $node->clone();
251              
252             # We want to make sure that we have locations prior to things being split
253             # up, if we can, but don't worry about it if we don't.
254 10         4527 eval { $node->location(); };
  10         27  
255              
256 10 100       13044 if ( my $single_namespace = _split_ppi_node_by_namespace_single($node) ) {
257 2         31 return $single_namespace;
258             }
259              
260 8         11 my %nodes_by_namespace;
261 8         21 _split_ppi_node_by_namespace_in_lexical_scope(
262             $node, 'main', undef, \%nodes_by_namespace,
263             );
264              
265 8         26 return \%nodes_by_namespace;
266             }
267              
268             # Handle the case where there's only one.
269             sub _split_ppi_node_by_namespace_single {
270 10     10   18 my ($node) = @_;
271              
272 10         29 my $package_statements = $node->find('PPI::Statement::Package');
273              
274 10 100 66     13610 if ( not $package_statements or not @{$package_statements} ) {
  9         45  
275 1         6 return { main => [$node] };
276             }
277              
278 9 100       14 if (@{$package_statements} == 1) {
  9         22  
279 6         11 my $package_statement = $package_statements->[0];
280 6         16 my $package_address = refaddr $package_statement;
281              
282             # Yes, child and not schild.
283 6         21 my $first_child = $node->child(0);
284 6 100 66     78 if (
      66        
285             $package_address == refaddr $node
286             or $first_child and $package_address == refaddr $first_child
287             ) {
288 1         5 return { $package_statement->namespace() => [$node] };
289             }
290             }
291              
292 8         23 return undef;
293             }
294              
295              
296             sub _split_ppi_node_by_namespace_in_lexical_scope {
297 18     18   35 my ($node, $initial_namespace, $initial_fragment, $nodes_by_namespace)
298             = @_;
299              
300 18         21 my %scope_fragments_by_namespace;
301              
302             # I certainly hope a value isn't going to exist at address 0.
303 18   100     47 my $initial_fragment_address = refaddr $initial_fragment || 0;
304 18         32 my ($namespace, $fragment) = ($initial_namespace, $initial_fragment);
305              
306 18 100       35 if ($initial_fragment) {
307 10         16 $scope_fragments_by_namespace{$namespace} = $initial_fragment;
308             }
309              
310 18         42 foreach my $child ( $node->children() ) {
311 151 100 100     2092 if ( $child->isa('PPI::Statement::Package') ) {
    100 100        
312 20 100       36 if ($fragment) {
313 19         37 _push_fragment($nodes_by_namespace, $namespace, $fragment);
314              
315 19         21 undef $fragment;
316             }
317              
318 20         61 $namespace = $child->namespace();
319             } elsif (
320             $child->isa('PPI::Statement::Compound')
321             or $child->isa('PPI::Statement::Given')
322             or $child->isa('PPI::Statement::When')
323             ) {
324 10         13 my $block;
325 10         33 my @components = $child->children();
326 10   66     77 while (not $block and my $component = shift @components) {
327 44 100       163 if ( $component->isa('PPI::Structure::Block') ) {
328 10         24 $block = $component;
329             }
330             }
331              
332 10 50       24 if ($block) {
333 10 100       22 if (not $fragment) {
334 1         3 $fragment = _get_fragment_for_split_ppi_node(
335             $nodes_by_namespace,
336             \%scope_fragments_by_namespace,
337             $namespace,
338             );
339             }
340              
341             _split_ppi_node_by_namespace_in_lexical_scope(
342 10         26 $block, $namespace, $fragment, $nodes_by_namespace,
343             );
344             }
345             }
346              
347 151         631 $fragment = _get_fragment_for_split_ppi_node(
348             $nodes_by_namespace, \%scope_fragments_by_namespace, $namespace,
349             );
350              
351 151 100       288 if ($initial_fragment_address != refaddr $fragment) {
352             # Need to fix these to use exceptions. Thankfully the P::C tests
353             # will insist that this happens.
354 125 50       227 $child->remove() or die 'Could not remove child from parent.';
355 125 50       3183 $fragment->add_element($child) or die 'Could not add child to fragment.';
356             }
357             }
358              
359 18         229 return;
360             }
361              
362             sub _get_fragment_for_split_ppi_node {
363 152     152   222 my ($nodes_by_namespace, $scope_fragments_by_namespace, $namespace) = @_;
364              
365 152         156 my $fragment;
366 152 100       305 if ( not $fragment = $scope_fragments_by_namespace->{$namespace} ) {
367 22         56 $fragment = PPI::Document::Fragment->new();
368 22         387 $scope_fragments_by_namespace->{$namespace} = $fragment;
369 22         36 _push_fragment($nodes_by_namespace, $namespace, $fragment);
370             }
371              
372 152         210 return $fragment;
373             }
374              
375             # Due to $fragment being passed into recursive calls to
376             # _split_ppi_node_by_namespace_in_lexical_scope(), we can end up attempting to
377             # put the same fragment into a namespace's nodes multiple times.
378             sub _push_fragment {
379 41     41   57 my ($nodes_by_namespace, $namespace, $fragment) = @_;
380              
381 41   100     94 my $nodes = $nodes_by_namespace->{$namespace} ||= [];
382              
383 41 100 100     45 if (not @{$nodes} or refaddr $nodes->[-1] != refaddr $fragment) {
  41         122  
384 22         26 push @{$nodes}, $fragment;
  22         34  
385             }
386              
387 41         57 return;
388             }
389             # End from PPIx::Utilities::Node
390              
391             1;
392              
393             =head1 NAME
394              
395             PPIx::Utils::Traversal - Utility functions for traversing PPI documents
396              
397             =head1 SYNOPSIS
398              
399             use PPIx::Utils::Traversal ':all';
400              
401             =head1 DESCRIPTION
402              
403             This package is a component of L that contains functions for
404             traversal of L documents.
405              
406             =head1 FUNCTIONS
407              
408             All functions can be imported by name, or with the tag C<:all>.
409              
410             =head2 first_arg
411              
412             my $first_arg = first_arg($element);
413              
414             Given a L that is presumed to be a function call (which
415             is usually a L), return the first argument. This is
416             similar of L and follows the same logic. Note that
417             for the code:
418              
419             int($x + 0.5)
420              
421             this function will return just the C<$x>, not the whole expression.
422             This is different from the behavior of L. Another
423             caveat is:
424              
425             int(($x + $y) + 0.5)
426              
427             which returns C<($x + $y)> as a L instance.
428              
429             =head2 parse_arg_list
430              
431             my @args = parse_arg_list($element);
432              
433             Given a L that is presumed to be a function call (which
434             is usually a L), splits the argument expressions
435             into arrays of tokens. Returns a list containing references to each
436             of those arrays. This is useful because parentheses are optional when
437             calling a function, and PPI parses them very differently. So this
438             method is a poor-man's parse tree of PPI nodes. It's not bullet-proof
439             because it doesn't respect precedence. In general, I don't like the
440             way this function works, so don't count on it to be stable (or even
441             present).
442              
443             =head2 split_nodes_on_comma
444              
445             my @args = split_nodes_on_comma(@nodes);
446              
447             This has the same return type as L but expects to be
448             passed the nodes that represent the interior of a list, like:
449              
450             'foo', 1, 2, 'bar'
451              
452             =head2 get_next_element_in_same_simple_statement
453              
454             my $element = get_next_element_in_same_simple_statement($element);
455              
456             Given a L, this subroutine returns the next element in
457             the same simple statement as defined by
458             L. If no next
459             element can be found, this subroutine simply returns C.
460              
461             If the $element is undefined or unblessed, we simply return C.
462              
463             If the $element satisfies
464             L, we return
465             C, B it has a parent which is a L.
466              
467             If the $element is the last significant element in its L,
468             we replace it with its parent and iterate again.
469              
470             Otherwise, we return C<< $element->snext_sibling() >>.
471              
472             =head2 get_previous_module_used_on_same_line
473              
474             my $element = get_previous_module_used_on_same_line($element);
475              
476             Given a L, returns the L representing the
477             name of the module included by the previous C or C on
478             the same line as the $element. If none is found, simply returns
479             C.
480              
481             For example, with the line
482              
483             use version; our $VERSION = ...;
484              
485             given the L instance for C<$VERSION>, this will
486             return "version".
487              
488             If the given element is in a C or , the return is from
489             the previous C or C on the line, if any.
490              
491             =head2 get_constant_name_elements_from_declaring_statement
492              
493             my @constants = get_constant_name_elements_from_declaring_statement($statement);
494              
495             Given a L, if the statement is a L, L, or
496             L declaration statement or a C, returns the names
497             of the things being defined.
498              
499             Given
500              
501             use constant 1.16 FOO => 'bar';
502              
503             this will return the L containing C<'FOO'>.
504             Given
505              
506             use constant 1.16 { FOO => 'bar', 'BAZ' => 'burfle' };
507              
508             this will return a list of the Ls containing C<'FOO'> and C<'BAZ'>.
509             Similarly, given
510              
511             Readonly::Hash my %FOO => ( bar => 'baz' );
512              
513             or
514              
515             const my %FOO => ( bar => 'baz' );
516              
517             this will return the L containing C<'%FOO'>.
518              
519             =head2 split_ppi_node_by_namespace
520              
521             my $subtrees = split_ppi_node_by_namespace($node);
522              
523             Returns the sub-trees for each namespace in the node as a reference to a hash
524             of references to arrays of Ls. Say we've got the following code:
525              
526             #!perl
527              
528             my $x = blah();
529              
530             package Foo;
531              
532             my $y = blah_blah();
533              
534             {
535             say 'Whee!';
536              
537             package Bar;
538              
539             something();
540             }
541              
542             thingy();
543              
544             package Baz;
545              
546             da_da_da();
547              
548             package Foo;
549              
550             foreach ( blrfl() ) {
551             ...
552             }
553              
554             Calling this function on a L for the above returns a
555             value that looks like this, using multi-line string literals for the
556             actual code parts instead of PPI trees to make this easier to read:
557              
558             {
559             main => [
560             q<
561             #!perl
562              
563             my $x = blah();
564             >,
565             ],
566             Foo => [
567             q<
568             package Foo;
569              
570             my $y = blah_blah();
571              
572             {
573             say 'Whee!';
574              
575             }
576              
577             thingy();
578             >,
579             q<
580             package Foo;
581              
582             foreach ( blrfl() ) {
583             ...
584             }
585             >,
586             ],
587             Bar => [
588             q<
589             package Bar;
590              
591             something();
592             >,
593             ],
594             Baz => [
595             q<
596             package Baz;
597              
598             da_da_da();
599             >,
600             ],
601             }
602              
603             Note that the return value contains copies of the original nodes, and not the
604             original nodes themselves due to the need to handle namespaces that are not
605             file-scoped. (Notice how the first element for "Foo" above differs from the
606             original code.)
607              
608             =head1 BUGS
609              
610             Report any issues on the public bugtracker.
611              
612             =head1 AUTHOR
613              
614             Dan Book
615              
616             Code originally from L by Jeffrey Ryan Thalhammer
617             , L and
618             L by Elliot Shank , and
619             L by Thomas R. Wyant, III
620              
621             =head1 COPYRIGHT AND LICENSE
622              
623             This software is copyright (c) 2005-2011 Imaginative Software Systems,
624             2007-2011 Elliot Shank, 2009-2010 Thomas R. Wyant, III, 2017 Dan Book.
625              
626             This is free software; you can redistribute it and/or modify it under
627             the same terms as the Perl 5 programming language system itself.
628              
629             =head1 SEE ALSO
630              
631             L, L, L