File Coverage

blib/lib/PPIx/Utils/Traversal.pm
Criterion Covered Total %
statement 133 180 73.8
branch 50 102 49.0
condition 28 66 42.4
subroutine 16 20 80.0
pod 7 7 100.0
total 234 375 62.4


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