File Coverage

blib/lib/PPIx/EditorTools/IntroduceTemporaryVariable.pm
Criterion Covered Total %
statement 60 60 100.0
branch 20 26 76.9
condition 6 9 66.6
subroutine 8 8 100.0
pod 0 1 0.0
total 94 104 90.3


line stmt bran cond sub pod time code
1             package PPIx::EditorTools::IntroduceTemporaryVariable;
2             our $AUTHORITY = 'cpan:YANICK';
3             # ABSTRACT: Introduces a temporary variable using PPI
4             $PPIx::EditorTools::IntroduceTemporaryVariable::VERSION = '0.21';
5 2     2   165254 use 5.008;
  2         13  
6 2     2   7 use strict;
  2         4  
  2         32  
7 2     2   8 use warnings;
  2         2  
  2         55  
8 2     2   9 use Carp;
  2         3  
  2         96  
9              
10 2     2   9 use base 'PPIx::EditorTools';
  2         4  
  2         483  
11 2         62 use Class::XSAccessor accessors => {
12             'start_location' => 'start_location',
13             'end_location' => 'end_location',
14             'expression' => 'expression',
15             'location' => 'location',
16 2     2   12 };
  2         4  
17              
18              
19             sub introduce {
20 4     4 0 754 my ( $self, %args ) = @_;
21 4         20 $self->process_doc(%args);
22 4 50       18 my $start_loc = $args{start_location} or croak "start_location required";
23 4 50       9 my $end_loc = $args{end_location} or croak "end_location required";
24 4         8 my $varname = $args{varname};
25 4 100       12 $varname = 'tmp' if not defined $varname;
26 4 100       15 $varname = '$' . $varname if $varname !~ /^[\$\@\%]/;
27              
28 4         7 my $ppi = $self->ppi;
29 4         12 $ppi->flush_locations;
30              
31 4         1196 my $token = PPIx::EditorTools::find_token_at_location( $ppi, $start_loc );
32 4         169 $ppi->flush_locations;
33 4 50       1029 die "no token" unless $token;
34              
35 4         15 my $statement = $token->statement();
36 4 50       80 die "no statement" unless $statement;
37              
38             # walk up the PPI tree until we reach a sort of structure that's not a statement.
39             # FIXME: This may or may not be robust. A PPI::Statement claims to be what's
40             # defined as "statements" in perlsyn, but it's not! perlsyn says all statements
41             # end in a semicolon unless at the end of a block.
42             # For PPI, Statements can be part of others and thus don't necessarily have
43             # a semicolon.
44 4         5 while (1) {
45 6         15 my $parent = $statement->statement();
46 6 50       40 last if not defined $parent;
47 6 50       15 if ( $parent eq $statement ) { # exactly the same object, ie. is a statement already
48 6         741 $parent = $statement->parent(); # force the parent
49             last
50 6 100 66     62 if not $parent # stop if we're at a block or at the document level
      66        
51             or $parent->isa('PPI::Structure::Block')
52             or $parent->isa('PPI::Structure::Document');
53 5         48 $parent = $parent->statement(); # force it to be a statement
54             }
55 5 100 66     68 last if not $parent # stop if the parent isn't a statement
56             or not $parent->isa('PPI::Statement');
57 2         3 $statement = $parent;
58             }
59 4         11 my $location_for_insert = $statement->location;
60 4         6713 $self->location($location_for_insert);
61              
62             # TODO: split on a look behind \n so we keep the \n
63 4         14 my @code = map {"$_\n"} split( /\n/, $ppi->serialize );
  18         2874  
64              
65 4         9 my $expr;
66 4         9 for my $line_num ( $start_loc->[0] .. $end_loc->[0] ) {
67 5         11 my $line = $code[ $line_num - 1 ]; # 0 based index to 1 base line numbers
68              
69 5 100       13 substr( $line, $end_loc->[1] ) = '' if $line_num == $end_loc->[0];
70 5 100       14 substr( $line, 0, $start_loc->[1] - 1 ) = ''
71             if $line_num == $start_loc->[0];
72              
73 5         10 $expr .= $line;
74             }
75              
76 4         11 $self->expression($expr);
77              
78 4         7 my $indent = '';
79 4 100       21 $indent = $1 if $code[ $location_for_insert->[0] - 1 ] =~ /^(\s+)/;
80              
81 4         5 my $place_holder = 'XXXXX_PPIx_EDITOR_PLACE_HOLDER_XXXXX';
82 4         29 substr(
83             $code[ $location_for_insert->[0] - 1 ],
84             $location_for_insert->[1] - 1, 0
85             ) = sprintf( "my %s = %s;\n%s", $varname, $place_holder, $indent );
86              
87             # TODO: need to watch for word boundries etc...
88 4         13 my $code = join( '', @code );
89 4         66 $code =~ s/\Q$expr\E/$varname/gm;
90 4         28 $code =~ s/\Q$place_holder\E/$expr/gm;
91              
92             return PPIx::EditorTools::ReturnObject->new(
93             code => $code,
94             element => sub {
95 2     2   7 PPIx::EditorTools::find_token_at_location(
96             shift->ppi,
97             $location_for_insert
98             );
99             }
100 4         30 );
101             }
102              
103             1;
104              
105             =pod
106              
107             =encoding UTF-8
108              
109             =head1 NAME
110              
111             PPIx::EditorTools::IntroduceTemporaryVariable - Introduces a temporary variable using PPI
112              
113             =head1 VERSION
114              
115             version 0.21
116              
117             =head1 SYNOPSIS
118              
119             my $munged = PPIx::EditorTools::IntroduceTemporaryVariable->new->introduce(
120             code => "use strict; BEGIN {
121             $^W = 1;
122             }\n\tmy $x = ( 1 + 10 / 12 ) * 2;\n\tmy $y = ( 3 + 10 / 12 ) * 2;\n",
123             start_location => [ 2, 19 ],
124             end_location => [ 2, 25 ],
125             varname => '$foo',
126             );
127             my $modified_code_as_string = $munged->code;
128             my $location_of_new_var_declaration = $munged->element->location;
129              
130             =head1 DESCRIPTION
131              
132             Given a region of code within a statement, replaces all occurrences of
133             that code with a temporary variable. Declares and initializes the
134             temporary variable right above the statement that included the
135             selected expression.
136              
137             =head1 METHODS
138              
139             =over 4
140              
141             =item new()
142              
143             Constructor. Generally shouldn't be called with any arguments.
144              
145             =item find( ppi => PPI::Document, start_location => Int, end_location => Int, varname => Str )
146              
147             =item find( code => Str, start_location => Int, end_location => Int, varname => Str )
148              
149             Accepts either a C to process or a string containing
150             the code (which will be converted into a C) to process.
151              
152             Given the region of code specified by start_location and end_location,
153             replaces that code with a temporary variable with the name given
154             in varname (defaults to C). Declares and initializes
155             the temporary variable right above the statement that included the
156             selected expression.
157              
158             Returns a C with the modified code
159             as a string available via the C accessor (or as a C
160             via the C accessor), and the C where the new variable
161             is declared available via the C accessor.
162              
163             Croaks with a "no token" exception if no token is found at the location.
164             Croaks with a "no statement" exception if unable to find the statement.
165              
166             =back
167              
168             =head1 SEE ALSO
169              
170             This class inherits from C.
171             Also see L, L, and L.
172              
173             =head1 AUTHORS
174              
175             =over 4
176              
177             =item *
178              
179             Steffen Mueller C
180              
181             =item *
182              
183             Mark Grimes C
184              
185             =item *
186              
187             Ahmad M. Zawawi
188              
189             =item *
190              
191             Gabor Szabo
192              
193             =item *
194              
195             Yanick Champoux
196              
197             =back
198              
199             =head1 COPYRIGHT AND LICENSE
200              
201             This software is copyright (c) 2017, 2014, 2012 by The Padre development team as listed in Padre.pm..
202              
203             This is free software; you can redistribute it and/or modify it under
204             the same terms as the Perl 5 programming language system itself.
205              
206             =cut
207              
208             __END__