File Coverage

blib/lib/Ref/Util/Rewriter.pm
Criterion Covered Total %
statement 74 80 92.5
branch 23 28 82.1
condition 6 9 66.6
subroutine 11 12 91.6
pod 3 3 100.0
total 117 132 88.6


line stmt bran cond sub pod time code
1             package Ref::Util::Rewriter;
2             # ABSTRACT: Rewrite your code to use Ref::Util
3              
4 1     1   13061 use strict;
  1         1  
  1         22  
5 1     1   3 use warnings;
  1         1  
  1         17  
6              
7 1     1   449 use PPI;
  1         95757  
  1         29  
8 1     1   369 use Safe::Isa;
  1         300  
  1         108  
9 1     1   4 use Exporter qw< import >;
  1         1  
  1         21  
10 1     1   2 use List::Util qw< first >;
  1         1  
  1         667  
11              
12             my @EXPORT_OK = qw< rewrite_string rewrite_file >;
13              
14             my %reftype_to_reffunc = (
15             SCALAR => 'is_scalarref',
16             ARRAY => 'is_arrayref',
17             HASH => 'is_hashref',
18             CODE => 'is_coderef',
19             Regexp => 'is_regexpref',
20             GLOB => 'is_globref',
21             IO => 'is_ioref',
22             REF => 'is_refref',
23             );
24              
25             sub rewrite_string {
26 6     6 1 1811 my $string = shift;
27 6         22 my $res = rewrite_doc( PPI::Document->new(\$string) );
28 6         656 return $res;
29             }
30              
31             sub rewrite_file {
32 0     0 1 0 my $file = shift;
33 0         0 return rewrite_doc( PPI::Document->new($file) );
34             }
35              
36             sub rewrite_doc {
37 6     6 1 6222 my $doc = shift;
38 6         15 my $all_statements = $doc->find('PPI::Statement');
39 6         2131 my @cond_ops = qw;
40 6         6 my @new_statements;
41              
42 6         7 foreach my $statement ( @{$all_statements} ) {
  6         11  
43             # if there's an "if()" statement, it appears as a Compound statement
44             # and then each internal statement appears again,
45             # causing duplication in results
46 12 100       123 $statement->$_isa('PPI::Statement::Compound')
47             and next;
48              
49             # find the 'ref' functions
50             my $ref_subs = $statement->find( sub {
51 66 100   66   605 $_[1]->isa('PPI::Token::Word') and $_[1]->content eq 'ref'
52 11 100       121 }) or next;
53              
54 6         52 my $statement_def;
55              
56             REF_STATEMENT:
57 6         3 foreach my $ref_sub ( @{$ref_subs} ) {
  6         10  
58             # we want to pick up everything until we find a delimiter
59             # effectively telling us we ended the parameters to "ref"
60 6         4 my $sib = $ref_sub;
61 6         3 my ( @func_args, $reffunc_doc, @rest_of_tokens );
62              
63 6         15 while ( $sib = $sib->next_sibling ) {
64             # end of statement/expression
65 25         287 my $content = $sib->content;
66 25 100       142 $content eq ';' and last;
67              
68             # we might already have a statement
69             # in this case collect all the rest of the tokens
70             # (this could be in two separate loops)
71 22 100       27 if ($statement_def) {
72 2         3 push @rest_of_tokens, $sib;
73 2         3 next;
74             }
75              
76             # reasons to stop
77 20 100 66     41 if ( ! $statement_def && $sib->$_isa('PPI::Token::Operator') ) {
78             # comparison operators
79 6 100 66     58 if ( $content eq 'eq' || $content eq 'ne' ) {
    50          
80             # "ARRAY" vs. $foo (which has "ARRAY" as value)
81             # we also move $sib to next significant sibling
82 5         11 my $val_token = $sib = $sib->snext_sibling;
83 5 50       70 my $val_str = $val_token->$_isa('PPI::Token::Quote')
84             ? $val_token->string
85             : $val_token->content;
86              
87 5         53 my $func = $reftype_to_reffunc{$val_str};
88 5 50       8 if ( !$func ) {
89 0         0 warn "Error: no match for $val_str\n";
90 0         0 next REF_STATEMENT;
91             }
92              
93 5         17 $statement_def = [ $func, \@func_args, '' ];
94 1     1   4 } elsif ( first { $content eq $_ } @cond_ops ) {
95             # is_ref
96              
97             # @func_args will now contain spaces too,
98             # which we will need to take out,
99             # in order to add them after the is_ref()
100             # reason those spaces don't appear in is_ref()
101             # we created is because we clean the function up
102 1         1 my $spaces_count = 0;
103 1         3 foreach my $idx ( reverse 0 .. $#func_args ) {
104 2 100       10 $func_args[$idx]->$_isa('PPI::Token::Whitespace')
105             ? $spaces_count++
106             : last;
107             }
108              
109             # we should add these *and* the cond op
110             # to the statement
111             # technically we can just add them at the end
112             # but it seems easier to stick them as strings
113             # and have them parsed
114             # (wish i understood PPI better)
115              
116             $statement_def = [
117 1         16 'is_ref',
118             \@func_args,
119             ' ' x $spaces_count . $content,
120             ];
121             } else {
122 0         0 warn "Warning: unknown operator: $sib\n";
123 0         0 next REF_STATEMENT;
124             }
125             } else {
126             # otherwise, collect it as a parameter
127 14         136 push @func_args, $sib;
128             }
129             }
130              
131             # skip when failed (error or warnings should appear from above)
132 6 50       40 $statement_def or next;
133              
134 6         5 my ( $func_name, $func_args, $rest ) = @{$statement_def};
  6         9  
135 6         8 $rest .= $_ for @rest_of_tokens;
136 6 100 66     19 $sib && $sib->content eq ';'
137             and $rest .= ';';
138              
139 6         20 $reffunc_doc = _create_statement(
140             $func_name, $func_args, $rest
141             );
142              
143             # prevent garbage collection
144             # FIXME: turn this into an interation that finds weaken
145             # objects and unweakens them (Scalar::Util::unweaken)
146 6         4149 push @new_statements, $reffunc_doc;
147              
148 6         19 my $new_statement = ( $reffunc_doc->children )[0];
149              
150 6         29 $ref_sub->parent->insert_before($new_statement);
151 6         169 $ref_sub->parent->remove;
152             }
153             }
154              
155 6         81 return "$doc";
156             }
157              
158             sub _create_statement {
159 6     6   6 my ( $func, $args, $rest ) = @_;
160 6         5 my $args_str = join '', @{$args};
  6         12  
161 6         106 $args_str =~ s/^\s+//;
162 6         18 $args_str =~ s/\s+$//;
163 6         11 $args_str =~ s/^\(+//;
164 6         9 $args_str =~ s/\)+$//;
165 6 50       12 defined $rest or $rest = '';
166 6         25 return PPI::Document::Fragment->new(\"$func($args_str)$rest");
167             }
168              
169             1;
170              
171             __END__