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   322074 use strict;
  3         4  
  3         79  
4 3     3   8 use warnings;
  3         4  
  3         160  
5 3     3   15 use Exporter 'import';
  3         6  
  3         94  
6 3     3   12 use PPI::Token::Quote::Single;
  3         4  
  3         104  
7 3     3   10 use PPI::Document::Fragment;
  3         3  
  3         76  
8 3     3   9 use Scalar::Util 'refaddr';
  3         8  
  3         120  
9              
10 3     3   1042 use PPIx::Utils::Language qw(precedence_of);
  3         7  
  3         167  
11 3         4762 use PPIx::Utils::_Common qw(
12             is_ppi_expression_or_generic_statement
13             is_ppi_simple_statement
14 3     3   955 );
  3         6  
15              
16             our $VERSION = '0.004';
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 223304 my $elem = shift;
34 7         70 my $sib = $elem->snext_sibling();
35 7 50       152 return undef if !$sib;
36              
37 7 100       20 if ( $sib->isa('PPI::Structure::List') ) {
38              
39 2         34 my $expr = $sib->schild(0);
40 2 100       21 return undef if !$expr;
41 1 50       4 return $expr->isa('PPI::Statement') ? $expr->schild(0) : $expr;
42             }
43              
44 5         9 return $sib;
45             }
46              
47             sub parse_arg_list {
48 11     11 1 10658 my $elem = shift;
49 11         27 my $sib = $elem->snext_sibling();
50 11 50       248 return() if !$sib;
51              
52 11 100       31 if ( $sib->isa('PPI::Structure::List') ) {
53              
54             #Pull siblings from list
55 3         20 my @list_contents = $sib->schildren();
56 3 50       27 return() if not @list_contents;
57              
58 3         3 my @list_expressions;
59 3         7 foreach my $item (@list_contents) {
60 3 50       8 if (
61             is_ppi_expression_or_generic_statement($item)
62             ) {
63 3         11 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         8 return @list_expressions;
73             }
74             else {
75              
76             #Gather up remaining nodes in the statement
77 8         8 my $iter = $elem;
78 8         9 my @arg_list = ();
79              
80 8         11 while ($iter = $iter->snext_sibling() ) {
81 19 100 66     294 last if $iter->isa('PPI::Token::Structure') and $iter eq ';';
82 11 50 66     30 last if $iter->isa('PPI::Token::Operator')
83             and $MIN_PRECEDENCE_TO_TERMINATE_PARENLESS_ARG_LIST <=
84             precedence_of( $iter );
85 11         34 push @arg_list, $iter;
86             }
87 8         117 return split_nodes_on_comma( @arg_list );
88             }
89             }
90              
91             sub split_nodes_on_comma {
92 11     11 1 48 my @nodes = @_;
93              
94 11         11 my $i = 0;
95 11         11 my @node_stacks;
96 11         13 for my $node (@nodes) {
97 26 100 66     117 if (
    100 66        
98             $node->isa('PPI::Token::Operator')
99             and ($node eq ',' or $node eq '=>')
100             ) {
101 8 50       93 if (@node_stacks) {
102 8         10 $i++; #Move forward to next 'node stack'
103             }
104 8         10 next;
105             } elsif ( $node->isa('PPI::Token::QuoteLike::Words' )) {
106 7         12 my $section = $node->{sections}->[0];
107 7         10 my @words = split ' ', substr $node->content, $section->{position}, $section->{size};
108 7         39 my $loc = $node->location;
109 7         3927 for my $word (@words) {
110 10         31 my $token = PPI::Token::Quote::Single->new(q{'} . $word . q{'});
111 10         111 $token->{_location} = $loc;
112 10         12 push @{ $node_stacks[$i++] }, $token;
  10         25  
113             }
114 7         14 next;
115             }
116 11         11 push @{ $node_stacks[$i] }, $node;
  11         20  
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 11738 my ($element) = @_;
161              
162 4 50       13 return() if not $element;
163 4 50       11 return() if not $element->isa('PPI::Statement');
164              
165 4 50 0     9 if ( $element->isa('PPI::Statement::Include') ) {
    0          
166 4         5 my $pragma;
167 4 50 33     11 if ( $pragma = $element->pragma() and $pragma eq 'constant' ) {
168 4         126 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   6 my ($include) = @_;
190              
191 4 50       7 my @arguments = $include->arguments() or return();
192              
193 4         113 my $follower = $arguments[0];
194 4 50       7 return() if not defined $follower;
195              
196 4 100 66     14 if ($follower->isa('PPI::Token::Operator') && $follower->content eq '+') {
197 1         5 $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     15 if (
207             $follower->isa( 'PPI::Structure::Constructor' )
208             or $follower->isa( 'PPI::Structure::Block' )
209             ) {
210 3 50       8 my $statement = $follower->schild( 0 ) or return();
211 3 50       162 $statement->isa( 'PPI::Statement' ) or return();
212              
213 3         4 my @elements;
214 3         4 my $inx = 0;
215 3         6 foreach my $child ( $statement->schildren() ) {
216 17 100       69 if (not $inx % 2) {
217 10   100     8 push @{ $elements[ $inx ] ||= [] }, $child;
  10         24  
218             }
219              
220 17 100       24 if ( $IS_COMMA{ $child->content() } ) {
221 7         17 $inx++;
222             }
223             }
224              
225             return map
226             {
227 3         11 (
228             $_
229 7 100 33     20 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         3 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 340140 my ($node) = @_;
248              
249             # Ensure we don't screw up the original.
250 10         35 $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         4078 eval { $node->location(); };
  10         29  
255              
256 10 100       11334 if ( my $single_namespace = _split_ppi_node_by_namespace_single($node) ) {
257 2         31 return $single_namespace;
258             }
259              
260 8         10 my %nodes_by_namespace;
261 8         22 _split_ppi_node_by_namespace_in_lexical_scope(
262             $node, 'main', undef, \%nodes_by_namespace,
263             );
264              
265 8         24 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     11622 if ( not $package_statements or not @{$package_statements} ) {
  9         40  
275 1         5 return { main => [$node] };
276             }
277              
278 9 100       11 if (@{$package_statements} == 1) {
  9         18  
279 6         11 my $package_statement = $package_statements->[0];
280 6         11 my $package_address = refaddr $package_statement;
281              
282             # Yes, child and not schild.
283 6         19 my $first_child = $node->child(0);
284 6 100 66     66 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         22 return undef;
293             }
294              
295              
296             sub _split_ppi_node_by_namespace_in_lexical_scope {
297 18     18   30 my ($node, $initial_namespace, $initial_fragment, $nodes_by_namespace)
298             = @_;
299              
300 18         19 my %scope_fragments_by_namespace;
301              
302             # I certainly hope a value isn't going to exist at address 0.
303 18   100     34 my $initial_fragment_address = refaddr $initial_fragment || 0;
304 18         42 my ($namespace, $fragment) = ($initial_namespace, $initial_fragment);
305              
306 18 100       50 if ($initial_fragment) {
307 10         18 $scope_fragments_by_namespace{$namespace} = $initial_fragment;
308             }
309              
310 18         44 foreach my $child ( $node->children() ) {
311 151 100 100     1666 if ( $child->isa('PPI::Statement::Package') ) {
    100 100        
312 20 100       35 if ($fragment) {
313 19         30 _push_fragment($nodes_by_namespace, $namespace, $fragment);
314              
315 19         19 undef $fragment;
316             }
317              
318 20         42 $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         10 my $block;
325 10         29 my @components = $child->children();
326 10   66     67 while (not $block and my $component = shift @components) {
327 44 100       135 if ( $component->isa('PPI::Structure::Block') ) {
328 10         24 $block = $component;
329             }
330             }
331              
332 10 50       18 if ($block) {
333 10 100       16 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         27 $block, $namespace, $fragment, $nodes_by_namespace,
343             );
344             }
345             }
346              
347 151         545 $fragment = _get_fragment_for_split_ppi_node(
348             $nodes_by_namespace, \%scope_fragments_by_namespace, $namespace,
349             );
350              
351 151 100       224 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       194 $child->remove() or die 'Could not remove child from parent.';
355 125 50       3382 $fragment->add_element($child) or die 'Could not add child to fragment.';
356             }
357             }
358              
359 18         161 return;
360             }
361              
362             sub _get_fragment_for_split_ppi_node {
363 152     152   189 my ($nodes_by_namespace, $scope_fragments_by_namespace, $namespace) = @_;
364              
365 152         138 my $fragment;
366 152 100       265 if ( not $fragment = $scope_fragments_by_namespace->{$namespace} ) {
367 22         70 $fragment = PPI::Document::Fragment->new();
368 22         343 $scope_fragments_by_namespace->{$namespace} = $fragment;
369 22         31 _push_fragment($nodes_by_namespace, $namespace, $fragment);
370             }
371              
372 152         163 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   53 my ($nodes_by_namespace, $namespace, $fragment) = @_;
380              
381 41   100     81 my $nodes = $nodes_by_namespace->{$namespace} ||= [];
382              
383 41 100 100     38 if (not @{$nodes} or refaddr $nodes->[-1] != refaddr $fragment) {
  41         91  
384 22         22 push @{$nodes}, $fragment;
  22         37  
385             }
386              
387 41         55 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<PPIx::Utils> that contains functions for
404             traversal of L<PPI> 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<PPI::Element> that is presumed to be a function call (which
415             is usually a L<PPI::Token::Word>), return the first argument. This is
416             similar of L</parse_arg_list> 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</parse_arg_list>. Another
423             caveat is:
424              
425             int(($x + $y) + 0.5)
426              
427             which returns C<($x + $y)> as a L<PPI::Structure::List> instance.
428              
429             =head2 parse_arg_list
430              
431             my @args = parse_arg_list($element);
432              
433             Given a L<PPI::Element> that is presumed to be a function call (which
434             is usually a L<PPI::Token::Word>), 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</parse_arg_list> 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<PPI::Element>, this subroutine returns the next element in
457             the same simple statement as defined by
458             L<PPIx::Utils::Classification/is_ppi_simple_statement>. If no next
459             element can be found, this subroutine simply returns C<undef>.
460              
461             If the $element is undefined or unblessed, we simply return C<undef>.
462              
463             If the $element satisfies
464             L<PPIx::Utils::Classification/is_ppi_simple_statement>, we return
465             C<undef>, B<unless> it has a parent which is a L<PPI::Structure::List>.
466              
467             If the $element is the last significant element in its L<PPI::Node>,
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<PPI::Element>, returns the L<PPI::Element> representing the
477             name of the module included by the previous C<use> or C<require> on
478             the same line as the $element. If none is found, simply returns
479             C<undef>.
480              
481             For example, with the line
482              
483             use version; our $VERSION = ...;
484              
485             given the L<PPI::Token::Symbol> instance for C<$VERSION>, this will
486             return "version".
487              
488             If the given element is in a C<use> or <require>, the return is from
489             the previous C<use> or C<require> 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<PPI::Statement>, if the statement is a L<Readonly>, L<ReadonlyX>, or
496             L<Const::Fast> declaration statement or a C<use constant>, 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<PPI::Token::Word> containing C<'FOO'>.
504             Given
505              
506             use constant 1.16 { FOO => 'bar', 'BAZ' => 'burfle' };
507              
508             this will return a list of the L<PPI::Token>s 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<PPI::Token::Symbol> 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 L<PPI::Node>s. 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<PPI::Document> 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 <dbook@cpan.org>
615              
616             Code originally from L<Perl::Critic::Utils> by Jeffrey Ryan Thalhammer
617             <jeff@imaginative-software.com>, L<Perl::Critic::Utils::PPI> and
618             L<PPIx::Utilities::Node> by Elliot Shank <perl@galumph.com>, and
619             L<PPIx::Utilities::Statement> by Thomas R. Wyant, III <wyant@cpan.org>
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<Perl::Critic::Utils>, L<Perl::Critic::Utils::PPI>, L<PPIx::Utilities>