package PPIx::EditorTools::IntroduceTemporaryVariable;
# ABSTRACT: Introduces a temporary variable using PPI
use 5.008;
use strict;
use warnings;
use Carp;
use base 'PPIx::EditorTools';
use Class::XSAccessor accessors => {
'start_location' => 'start_location',
'end_location' => 'end_location',
'expression' => 'expression',
'location' => 'location',
};
our $VERSION = '0.18';
=pod
=head1 SYNOPSIS
my $munged = PPIx::EditorTools::IntroduceTemporaryVariable->new->introduce(
code => "use strict; BEGIN {
$^W = 1;
}\n\tmy $x = ( 1 + 10 / 12 ) * 2;\n\tmy $y = ( 3 + 10 / 12 ) * 2;\n",
start_location => [ 2, 19 ],
end_location => [ 2, 25 ],
varname => '$foo',
);
my $modified_code_as_string = $munged->code;
my $location_of_new_var_declaration = $munged->element->location;
=head1 DESCRIPTION
Given a region of code within a statement, replaces all occurrences of
that code with a temporary variable. Declares and initializes the
temporary variable right above the statement that included the
selected expression.
=head1 METHODS
=over 4
=item new()
Constructor. Generally shouldn't be called with any arguments.
=item find( ppi => PPI::Document, start_location => Int, end_location => Int, varname => Str )
=item find( code => Str, start_location => Int, end_location => Int, varname => Str )
Accepts either a C<PPI::Document> to process or a string containing
the code (which will be converted into a C<PPI::Document>) to process.
Given the region of code specified by start_location and end_location,
replaces that code with a temporary variable with the name given
in varname (defaults to C<tmp>). Declares and initializes
the temporary variable right above the statement that included the
selected expression.
Returns a C<PPIx::EditorTools::ReturnObject> with the modified code
as a string available via the C<code> accessor (or as a C<PPI::Document>
via the C<ppi> accessor), and the C<PPI::Token> where the new variable
is declared available via the C<element> accessor.
Croaks with a "no token" exception if no token is found at the location.
Croaks with a "no statement" exception if unable to find the statement.
=back
=cut
sub introduce {
my ( $self, %args ) = @_;
$self->process_doc(%args);
my $start_loc = $args{start_location} or croak "start_location required";
my $end_loc = $args{end_location} or croak "end_location required";
my $varname = $args{varname};
$varname = 'tmp' if not defined $varname;
$varname = '$' . $varname if $varname !~ /^[\$\@\%]/;
my $ppi = $self->ppi;
$ppi->flush_locations;
my $token = PPIx::EditorTools::find_token_at_location( $ppi, $start_loc );
$ppi->flush_locations;
die "no token" unless $token;
my $statement = $token->statement();
die "no statement" unless $statement;
# walk up the PPI tree until we reach a sort of structure that's not a statement.
# FIXME: This may or may not be robust. A PPI::Statement claims to be what's
# defined as "statements" in perlsyn, but it's not! perlsyn says all statements
# end in a semicolon unless at the end of a block.
# For PPI, Statements can be part of others and thus don't necessarily have
# a semicolon.
while (1) {
my $parent = $statement->statement();
last if not defined $parent;
if ( $parent eq $statement ) { # exactly the same object, ie. is a statement already
$parent = $statement->parent(); # force the parent
last
if not $parent # stop if we're at a block or at the document level
or $parent->isa('PPI::Structure::Block')
or $parent->isa('PPI::Structure::Document');
$parent = $parent->statement(); # force it to be a statement
}
last if not $parent # stop if the parent isn't a statement
or not $parent->isa('PPI::Statement');
$statement = $parent;
}
my $location_for_insert = $statement->location;
$self->location($location_for_insert);
# TODO: split on a look behind \n so we keep the \n
my @code = map {"$_\n"} split( /\n/, $ppi->serialize );
my $expr;
for my $line_num ( $start_loc->[0] .. $end_loc->[0] ) {
my $line = $code[ $line_num - 1 ]; # 0 based index to 1 base line numbers
substr( $line, $end_loc->[1] ) = '' if $line_num == $end_loc->[0];
substr( $line, 0, $start_loc->[1] - 1 ) = ''
if $line_num == $start_loc->[0];
$expr .= $line;
}
$self->expression($expr);
my $indent = '';
$indent = $1 if $code[ $location_for_insert->[0] - 1 ] =~ /^(\s+)/;
my $place_holder = 'XXXXX_PPIx_EDITOR_PLACE_HOLDER_XXXXX';
substr(
$code[ $location_for_insert->[0] - 1 ],
$location_for_insert->[1] - 1, 0
) = sprintf( "my %s = %s;\n%s", $varname, $place_holder, $indent );
# TODO: need to watch for word boundries etc...
my $code = join( '', @code );
$code =~ s/\Q$expr\E/$varname/gm;
$code =~ s/\Q$place_holder\E/$expr/gm;
return PPIx::EditorTools::ReturnObject->new(
code => $code,
element => sub {
PPIx::EditorTools::find_token_at_location(
shift->ppi,
$location_for_insert
);
}
);
}
1;
__END__
=head1 SEE ALSO
This class inherits from C<PPIx::EditorTools>.
Also see L<App::EditorTools>, L<Padre>, and L<PPI>.
=cut
# Copyright 2008-2009 The Padre development team as listed in Padre.pm.
# LICENSE
# This program is free software; you can redistribute it and/or
# modify it under the same terms as Perl 5 itself.