File Coverage

blib/lib/PPIx/EditorTools/RenameVariable.pm
Criterion Covered Total %
statement 93 103 90.2
branch 36 52 69.2
condition 14 24 58.3
subroutine 11 11 100.0
pod 1 1 100.0
total 155 191 81.1


line stmt bran cond sub pod time code
1             package PPIx::EditorTools::RenameVariable;
2             our $AUTHORITY = 'cpan:YANICK';
3             # ABSTRACT: Lexically replace a variable name in Perl code
4             $PPIx::EditorTools::RenameVariable::VERSION = '0.21';
5 3     3   344447 use 5.008;
  3         20  
6 3     3   13 use strict;
  3         4  
  3         48  
7 3     3   22 use warnings;
  3         10  
  3         82  
8 3     3   15 use Carp;
  3         4  
  3         145  
9              
10 3     3   15 use base 'PPIx::EditorTools';
  3         5  
  3         864  
11 3     3   17 use Class::XSAccessor;
  3         5  
  3         23  
12              
13              
14             sub rename {
15 8     8 1 797976 my ( $self, %args ) = @_;
16 8         89 $self->process_doc(%args);
17 8   33     38 my $column = $args{column} || croak "column required";
18 8   33     25 my $line = $args{line} || croak "line required";
19 8         20 my $location = [ $line, $column ];
20 8         19 my $replacement = $args{replacement};
21 8 50 100     108 if ( ( $args{to_camel_case} or $args{from_camel_case} )
    50 66        
      66        
22             and defined $replacement )
23             {
24 0         0 croak("Can't accept both replacement name and to_camel_case/from_camel_case");
25             } elsif ( not $args{to_camel_case}
26             and not $args{from_camel_case}
27             and not defined $replacement )
28             {
29 0         0 croak("Need either 'replacement' or to/from_camel_case options");
30             }
31              
32 8         22 my $doc = $self->ppi;
33 8         29 my $token = PPIx::EditorTools::find_token_at_location( $doc, $location );
34              
35 8 50       469 die "no token found" unless defined $token;
36              
37 8         44 my $declaration = PPIx::EditorTools::find_variable_declaration($token);
38 8 50       35 die "no declaration" unless defined $declaration;
39              
40 8         27 $doc->index_locations;
41              
42 8         16675 my $scope = $declaration;
43 8   66     79 while ( not $scope->isa('PPI::Document')
44             and not $scope->isa('PPI::Structure::Block') )
45             {
46 8         34 $scope = $scope->parent;
47             }
48              
49 8         70 my $varname = $token->symbol;
50 8 100       380 if ( not defined $replacement ) {
51 4 100       11 if ( $args{from_camel_case} ) {
52 2         8 $replacement = _from_camel_case( $varname, $args{ucfirst} );
53             } else { # $args{to_camel_case}
54 2         14 $replacement = _to_camel_case( $varname, $args{ucfirst} );
55             }
56 4 50       11 if ( $varname eq $replacement ) {
57 0         0 return PPIx::EditorTools::ReturnObject->new(
58             ppi => $doc,
59             element => $token
60             );
61             }
62             }
63              
64             #warn "VARNAME: $varname";
65              
66             # TODO: This could be part of PPI somehow?
67             # The following string of hacks is simply for finding symbols in quotelikes and regexes
68 8         18 my $type = substr( $varname, 0, 1 );
69 8 100       26 my $brace = $type eq '@' ? '[' : ( $type eq '%' ? '{' : '' );
    50          
70              
71 8         10 my @patterns;
72 8 100 66     55 if ( $type eq '@' or $type eq '%' ) {
73 2         6 my $accessv = $varname;
74 2         69 $accessv =~ s/^\Q$type\E/\$/;
75 2         9 @patterns = (
76             quotemeta( _curlify($varname) ),
77             quotemeta($varname),
78             quotemeta($accessv) . '(?=' . quotemeta($brace) . ')',
79             );
80 2 50       7 if ( $type eq '%' ) {
    0          
81 2         4 my $slicev = $varname;
82 2         24 $slicev =~ s/^\%/\@/;
83 2         11 push @patterns, quotemeta($slicev) . '(?=' . quotemeta($brace) . ')';
84             } elsif ( $type eq '@' ) {
85 0         0 my $indexv = $varname;
86 0         0 $indexv =~ s/^\@/\$\#/;
87 0         0 push @patterns, quotemeta($indexv);
88             }
89             } else {
90 6         26 @patterns = (
91             quotemeta( _curlify($varname) ),
92             quotemeta($varname) . "(?![\[\{])"
93             );
94             }
95 8         12 my %unique;
96 8         20 my $finder_regexp = '(?:' . join( '|', grep { !$unique{$_}++ } @patterns ) . ')';
  20         76  
97              
98 8         179 $finder_regexp = qr/$finder_regexp/; # used to find symbols in quotelikes and regexes
99             #warn $finder_regexp;
100              
101 8         46 $replacement =~ s/^\W+//;
102              
103             $scope->find(
104             sub {
105 468     468   4695 my $node = $_[1];
106 468 100 33     1488 if ( $node->isa("PPI::Token::Symbol") ) {
    50          
    100          
107 44 100       88 return 0 unless $node->symbol eq $varname;
108              
109             # TODO do this without breaking encapsulation!
110 18         708 $node->{content} = substr( $node->content(), 0, 1 ) . $replacement;
111             }
112              
113             # This used to be a simple "if". Patrickas: "[elsif] resolves this
114             # issue but it may introduce other bugs since I am not sure I
115             # understand the code that follows it."
116             # See Padre trac ticket #655 for the full comment. Remove this
117             # comment if there are new bugs resulting from this change.
118             elsif ( $type eq '@' and $node->isa("PPI::Token::ArrayIndex") ) { # $#foo
119 0 0       0 return 0
120             unless substr( $node->content, 2 ) eq substr( $varname, 1 );
121              
122             # TODO do this without breaking encapsulation!
123 0         0 $node->{content} = '$#' . $replacement;
124             } elsif ( $node->isa("PPI::Token") ) { # the case of potential quotelikes and regexes
125 360         505 my $str = $node->content;
126 360 100       1649 if ($str =~ s{($finder_regexp)([\[\{]?)}<
127 4 50       28 if ($1 =~ tr/{//) { substr($1, 0, ($1=~tr/#//)+1) . "{$replacement}$2" }
  4         25  
128 0         0 else { substr($1, 0, ($1=~tr/#//)+1) . "$replacement$2" }
129             >ge
130             )
131             {
132              
133             # TODO do this without breaking encapsulation!
134 4         12 $node->{content} = $str;
135             }
136             }
137 442         704 return 0;
138             },
139 8         102 );
140              
141 8         189 return PPIx::EditorTools::ReturnObject->new(
142             ppi => $doc,
143             element => $token,
144             );
145             }
146              
147             # converts a variable name to camel case and optionally converts the
148             # first character to upper case
149             sub _to_camel_case {
150 38     38   5352 my $var = shift;
151 38         53 my $ucfirst = shift;
152 38         48 my $prefix;
153 38 100       145 if ( $var =~ s/^(\W*_)// ) {
154 6         13 $prefix = $1;
155             }
156 38         184 $var =~ s/_([[:alpha:]])/\u$1/g;
157 38 100       151 $var =~ s/^([^[:alpha:]]*)([[:alpha:]])/$1\u$2/ if $ucfirst;
158 38 100       78 $var = $prefix . $var if defined $prefix;
159 38         150 return $var;
160             }
161              
162             sub _from_camel_case {
163 56     56   7795 my $var = shift;
164 56         75 my $ucfirst = shift;
165 56         67 my $prefix;
166 56 50       567 if ( $var =~ s/^(\W*_?)// ) {
167 56         111 $prefix = $1;
168             }
169 56 100       107 if ($ucfirst) {
170 28         47 $var = lcfirst($var);
171 28         127 $var =~ s/([[:upper:]])/_\u$1/g;
172 28         117 $var =~ s/^([^[:alpha:]]*)([[:alpha:]])/$1\u$2/;
173             } else {
174 28         136 $var =~ s/^([^[:alpha:]]*)([[:alpha:]])/$1\l$2/;
175 28         107 $var =~ s/([[:upper:]])/_\l$1/g;
176             }
177 56 50       136 $var = $prefix . $var if defined $prefix;
178 56         240 return $var;
179             }
180              
181              
182             sub _curlify {
183 8     8   19 my $var = shift;
184 8 50       102 if ( $var =~ s/^([\$\@\%])(.+)$/${1}{$2}/ ) {
185 8         44 return ($var);
186             }
187 0           return ();
188             }
189              
190             1;
191              
192             __END__