File Coverage

blib/lib/PPIx/Utils/Traversal.pm
Criterion Covered Total %
statement 161 183 87.9
branch 69 106 65.0
condition 37 69 53.6
subroutine 18 20 90.0
pod 7 7 100.0
total 292 385 75.8


line stmt bran cond sub pod time code
1             package PPIx::Utils::Traversal;
2              
3 3     3   224711 use strict;
  3         19  
  3         75  
4 3     3   13 use warnings;
  3         4  
  3         80  
5 3     3   11 use Exporter 'import';
  3         6  
  3         79  
6 3     3   13 use PPI::Token::Quote::Single;
  3         5  
  3         87  
7 3     3   11 use PPI::Document::Fragment;
  3         4  
  3         67  
8 3     3   21 use Scalar::Util 'refaddr';
  3         9  
  3         141  
9              
10 3     3   1349 use PPIx::Utils::Language qw(precedence_of);
  3         8  
  3         427  
11 3         5470 use PPIx::Utils::_Common qw(
12             is_ppi_expression_or_generic_statement
13             is_ppi_simple_statement
14 3     3   1013 );
  3         5  
15              
16             our $VERSION = '0.002';
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 6086 my $elem = shift;
34 7         19 my $sib = $elem->snext_sibling();
35 7 50       160 return undef if !$sib;
36              
37 7 100       24 if ( $sib->isa('PPI::Structure::List') ) {
38              
39 2         16 my $expr = $sib->schild(0);
40 2 100       24 return undef if !$expr;
41 1 50       9 return $expr->isa('PPI::Statement') ? $expr->schild(0) : $expr;
42             }
43              
44 5         11 return $sib;
45             }
46              
47             sub parse_arg_list {
48 11     11 1 11828 my $elem = shift;
49 11         36 my $sib = $elem->snext_sibling();
50 11 50       240 return() if !$sib;
51              
52 11 100       47 if ( $sib->isa('PPI::Structure::List') ) {
53              
54             #Pull siblings from list
55 3         28 my @list_contents = $sib->schildren();
56 3 50       42 return() if not @list_contents;
57              
58 3         5 my @list_expressions;
59 3         5 foreach my $item (@list_contents) {
60 4 100       14 if (
61             is_ppi_expression_or_generic_statement($item)
62             ) {
63 3         22 push
64             @list_expressions,
65             split_nodes_on_comma( $item->schildren() );
66             }
67             else {
68 1         3 push @list_expressions, $item;
69             }
70             }
71              
72 3         10 return @list_expressions;
73             }
74             else {
75              
76             #Gather up remaining nodes in the statement
77 8         14 my $iter = $elem;
78 8         14 my @arg_list = ();
79              
80 8         26 while ($iter = $iter->snext_sibling() ) {
81 19 100 66     363 last if $iter->isa('PPI::Token::Structure') and $iter eq ';';
82 11 50 66     37 last if $iter->isa('PPI::Token::Operator')
83             and $MIN_PRECEDENCE_TO_TERMINATE_PARENLESS_ARG_LIST <=
84             precedence_of( $iter );
85 11         40 push @arg_list, $iter;
86             }
87 8         121 return split_nodes_on_comma( @arg_list );
88             }
89             }
90              
91             sub split_nodes_on_comma {
92 11     11 1 62 my @nodes = @_;
93              
94 11         64 my $i = 0;
95 11         12 my @node_stacks;
96 11         22 for my $node (@nodes) {
97 25 100 66     122 if (
    100 66        
98             $node->isa('PPI::Token::Operator')
99             and ($node eq ',' or $node eq '=>')
100             ) {
101 8 100       127 if (@node_stacks) {
102 7         11 $i++; #Move forward to next 'node stack'
103             }
104 8         15 next;
105             } elsif ( $node->isa('PPI::Token::QuoteLike::Words' )) {
106 7         12 my $section = $node->{sections}->[0];
107 7         19 my @words = split ' ', substr $node->content, $section->{position}, $section->{size};
108 7         52 my $loc = $node->location;
109 7         4722 for my $word (@words) {
110 10         41 my $token = PPI::Token::Quote::Single->new(q{'} . $word . q{'});
111 10         138 $token->{_location} = $loc;
112 10         14 push @{ $node_stacks[$i++] }, $token;
  10         26  
113             }
114 7         14 next;
115             }
116 10         14 push @{ $node_stacks[$i] }, $node;
  10         21  
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 12528 my ($element) = @_;
161              
162 4 50       16 return() if not $element;
163 4 50       14 return() if not $element->isa('PPI::Statement');
164              
165 4 50 0     12 if ( $element->isa('PPI::Statement::Include') ) {
    0          
166 4         7 my $pragma;
167 4 50 33     12 if ( $pragma = $element->pragma() and $pragma eq 'constant' ) {
168 4         146 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   8 my ($include) = @_;
190              
191 4 50       10 my @arguments = $include->arguments() or return();
192              
193 4         132 my $follower = $arguments[0];
194 4 50       9 return() if not defined $follower;
195              
196 4 100 66     17 if ($follower->isa('PPI::Token::Operator') && $follower->content eq '+') {
197 1         6 $follower = $arguments[1];
198 1 50       3 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     21 if (
207             $follower->isa( 'PPI::Structure::Constructor' )
208             or $follower->isa( 'PPI::Structure::Block' )
209             ) {
210 3 50       13 my $statement = $follower->schild( 0 ) or return();
211 3 50       155 $statement->isa( 'PPI::Statement' ) or return();
212              
213 3         4 my @elements;
214 3         5 my $inx = 0;
215 3         8 foreach my $child ( $statement->schildren() ) {
216 17 100       84 if (not $inx % 2) {
217 10   100     13 push @{ $elements[ $inx ] ||= [] }, $child;
  10         24  
218             }
219              
220 17 100       28 if ( $IS_COMMA{ $child->content() } ) {
221 7         25 $inx++;
222             }
223             }
224              
225             return map
226             {
227 3         13 (
228             $_
229 7 100 33     23 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 107258 my ($node) = @_;
248              
249             # Ensure we don't screw up the original.
250 10         36 $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         4892 eval { $node->location(); };
  10         27  
255              
256 10 100       13236 if ( my $single_namespace = _split_ppi_node_by_namespace_single($node) ) {
257 2         45 return $single_namespace;
258             }
259              
260 8         10 my %nodes_by_namespace;
261 8         29 _split_ppi_node_by_namespace_in_lexical_scope(
262             $node, 'main', undef, \%nodes_by_namespace,
263             );
264              
265 8         28 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   19 my ($node) = @_;
271              
272 10         35 my $package_statements = $node->find('PPI::Statement::Package');
273              
274 10 100 66     14269 if ( not $package_statements or not @{$package_statements} ) {
  9         43  
275 1         5 return { main => [$node] };
276             }
277              
278 9 100       14 if (@{$package_statements} == 1) {
  9         32  
279 6         11 my $package_statement = $package_statements->[0];
280 6         18 my $package_address = refaddr $package_statement;
281              
282             # Yes, child and not schild.
283 6         20 my $first_child = $node->child(0);
284 6 100 66     86 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         26 return undef;
293             }
294              
295              
296             sub _split_ppi_node_by_namespace_in_lexical_scope {
297 18     18   41 my ($node, $initial_namespace, $initial_fragment, $nodes_by_namespace)
298             = @_;
299              
300 18         20 my %scope_fragments_by_namespace;
301              
302             # I certainly hope a value isn't going to exist at address 0.
303 18   100     52 my $initial_fragment_address = refaddr $initial_fragment || 0;
304 18         31 my ($namespace, $fragment) = ($initial_namespace, $initial_fragment);
305              
306 18 100       38 if ($initial_fragment) {
307 10         18 $scope_fragments_by_namespace{$namespace} = $initial_fragment;
308             }
309              
310 18         50 foreach my $child ( $node->children() ) {
311 151 100 100     2165 if ( $child->isa('PPI::Statement::Package') ) {
    100 100        
312 20 100       42 if ($fragment) {
313 19         35 _push_fragment($nodes_by_namespace, $namespace, $fragment);
314              
315 19         28 undef $fragment;
316             }
317              
318 20         50 $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         29 my @components = $child->children();
326 10   66     85 while (not $block and my $component = shift @components) {
327 44 100       185 if ( $component->isa('PPI::Structure::Block') ) {
328 10         28 $block = $component;
329             }
330             }
331              
332 10 50       24 if ($block) {
333 10 100       24 if (not $fragment) {
334 1         5 $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         29 $block, $namespace, $fragment, $nodes_by_namespace,
343             );
344             }
345             }
346              
347 151         643 $fragment = _get_fragment_for_split_ppi_node(
348             $nodes_by_namespace, \%scope_fragments_by_namespace, $namespace,
349             );
350              
351 151 100       283 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       229 $child->remove() or die 'Could not remove child from parent.';
355 125 50       3250 $fragment->add_element($child) or die 'Could not add child to fragment.';
356             }
357             }
358              
359 18         221 return;
360             }
361              
362             sub _get_fragment_for_split_ppi_node {
363 152     152   224 my ($nodes_by_namespace, $scope_fragments_by_namespace, $namespace) = @_;
364              
365 152         166 my $fragment;
366 152 100       325 if ( not $fragment = $scope_fragments_by_namespace->{$namespace} ) {
367 22         60 $fragment = PPI::Document::Fragment->new();
368 22         383 $scope_fragments_by_namespace->{$namespace} = $fragment;
369 22         36 _push_fragment($nodes_by_namespace, $namespace, $fragment);
370             }
371              
372 152         205 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   71 my ($nodes_by_namespace, $namespace, $fragment) = @_;
380              
381 41   100     99 my $nodes = $nodes_by_namespace->{$namespace} ||= [];
382              
383 41 100 100     50 if (not @{$nodes} or refaddr $nodes->[-1] != refaddr $fragment) {
  41         129  
384 22         32 push @{$nodes}, $fragment;
  22         37  
385             }
386              
387 41         62 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