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.20';
5 2     2   167973 use 5.008;
  2         13  
6 2     2   10 use strict;
  2         4  
  2         33  
7 2     2   7 use warnings;
  2         4  
  2         44  
8 2     2   8 use Carp;
  2         3  
  2         95  
9              
10 2     2   10 use base 'PPIx::EditorTools';
  2         3  
  2         417  
11 2         14 use Class::XSAccessor accessors => {
12             'start_location' => 'start_location',
13             'end_location' => 'end_location',
14             'expression' => 'expression',
15             'location' => 'location',
16 2     2   13 };
  2         3  
17              
18              
19             sub introduce {
20 4     4 0 807 my ( $self, %args ) = @_;
21 4         19 $self->process_doc(%args);
22 4 50       14 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         5 my $varname = $args{varname};
25 4 100       10 $varname = 'tmp' if not defined $varname;
26 4 100       13 $varname = '$' . $varname if $varname !~ /^[\$\@\%]/;
27              
28 4         6 my $ppi = $self->ppi;
29 4         13 $ppi->flush_locations;
30              
31 4         1131 my $token = PPIx::EditorTools::find_token_at_location( $ppi, $start_loc );
32 4         162 $ppi->flush_locations;
33 4 50       1038 die "no token" unless $token;
34              
35 4         13 my $statement = $token->statement();
36 4 50       73 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         7 while (1) {
45 6         17 my $parent = $statement->statement();
46 6 50       43 last if not defined $parent;
47 6 50       13 if ( $parent eq $statement ) { # exactly the same object, ie. is a statement already
48 6         761 $parent = $statement->parent(); # force the parent
49             last
50 6 100 66     52 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         44 $parent = $parent->statement(); # force it to be a statement
54             }
55 5 100 66     67 last if not $parent # stop if the parent isn't a statement
56             or not $parent->isa('PPI::Statement');
57 2         4 $statement = $parent;
58             }
59 4         22 my $location_for_insert = $statement->location;
60 4         6782 $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         2921  
64              
65 4         8 my $expr;
66 4         9 for my $line_num ( $start_loc->[0] .. $end_loc->[0] ) {
67 5         9 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       12 substr( $line, 0, $start_loc->[1] - 1 ) = ''
71             if $line_num == $start_loc->[0];
72              
73 5         12 $expr .= $line;
74             }
75              
76 4         10 $self->expression($expr);
77              
78 4         4 my $indent = '';
79 4 100       21 $indent = $1 if $code[ $location_for_insert->[0] - 1 ] =~ /^(\s+)/;
80              
81 4         11 my $place_holder = 'XXXXX_PPIx_EDITOR_PLACE_HOLDER_XXXXX';
82 4         21 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         12 my $code = join( '', @code );
89 4         61 $code =~ s/\Q$expr\E/$varname/gm;
90 4         22 $code =~ s/\Q$place_holder\E/$expr/gm;
91              
92             return PPIx::EditorTools::ReturnObject->new(
93             code => $code,
94             element => sub {
95 2     2   6 PPIx::EditorTools::find_token_at_location(
96             shift->ppi,
97             $location_for_insert
98             );
99             }
100 4         27 );
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.20
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<PPI::Document> to process or a string containing
150             the code (which will be converted into a C<PPI::Document>) 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<tmp>). Declares and initializes
155             the temporary variable right above the statement that included the
156             selected expression.
157              
158             Returns a C<PPIx::EditorTools::ReturnObject> with the modified code
159             as a string available via the C<code> accessor (or as a C<PPI::Document>
160             via the C<ppi> accessor), and the C<PPI::Token> where the new variable
161             is declared available via the C<element> 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<PPIx::EditorTools>.
171             Also see L<App::EditorTools>, L<Padre>, and L<PPI>.
172              
173             =head1 AUTHORS
174              
175             =over 4
176              
177             =item *
178              
179             Steffen Mueller C<smueller@cpan.org>
180              
181             =item *
182              
183             Mark Grimes C<mgrimes@cpan.org>
184              
185             =item *
186              
187             Ahmad M. Zawawi <ahmad.zawawi@gmail.com>
188              
189             =item *
190              
191             Gabor Szabo <gabor@szabgab.com>
192              
193             =item *
194              
195             Yanick Champoux <yanick@cpan.org>
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__
209              
210              
211             # Copyright 2008-2009 The Padre development team as listed in Padre.pm.
212             # LICENSE
213             # This program is free software; you can redistribute it and/or
214             # modify it under the same terms as Perl 5 itself.