Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion lib/PPI/Document.pm
Original file line number Diff line number Diff line change
Expand Up @@ -862,7 +862,11 @@ sub normalized {
# The normalization process will utterly destroy and mangle
# anything passed to it, so we are going to only give it a
# clone of ourselves.
PPI::Normal->process( $_[0]->clone );
my $clone = $_[0]->clone;
# Location cache is positional, not semantic — strip it so
# normalization compares structure, not source positions.
$clone->flush_locations;
PPI::Normal->process( $clone );
}

=pod
Expand Down
6 changes: 5 additions & 1 deletion lib/PPI/Element.pm
Original file line number Diff line number Diff line change
Expand Up @@ -500,7 +500,11 @@ Nodes, there is more work involved to keep the parent-child links intact.
=cut

sub clone {
Clone::clone(shift);
my $self = shift;
# Index locations before deep copy so they survive cloning
my $doc = $self->document;
$doc->index_locations if $doc;
Clone::clone($self);
}

=pod
Expand Down
40 changes: 40 additions & 0 deletions t/ppi_element_clone_location.t
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
#!/usr/bin/perl

# Test that location data survives clone operations
# See https://github.com/Perl-Critic/PPI/issues/80

use lib 't/lib';
use PPI::Test::pragmas;

use PPI::Document ();
use PPI::Find ();
use Test::More tests => 11 + ( $ENV{AUTHOR_TESTING} ? 1 : 0 );
use Helper 'safe_new';

# Basic: clone a statement and check location
SCOPE: {
my $doc = safe_new \"f(\$x, \$z, \$x);";
my $stmt = $doc->find_first('PPI::Statement');
my $clone = $stmt->clone;
is $clone->line_number, 1, 'cloned statement has line_number';
}

# Clone a statement from a multi-line document
SCOPE: {
my $src = "my \$x = 1;\nmy \$y = 2;\n";
my $doc = safe_new \$src;
my @stmts = @{ $doc->find('PPI::Statement') || [] };
is scalar @stmts, 2, 'found two statements';

my $clone2 = $stmts[1]->clone;
is $clone2->line_number, 2, 'cloned second statement preserves line 2';
}

# Clone a leaf token and check location
SCOPE: {
my $doc = safe_new \"my \$x = 1;";
my $sym = $doc->find_first('PPI::Token::Symbol');
my $clone = $sym->clone;
is $clone->line_number, 1, 'cloned token has line_number';
is $clone->column_number, 4, 'cloned token has column_number';
}
13 changes: 7 additions & 6 deletions t/ppi_element_flush.t
Original file line number Diff line number Diff line change
Expand Up @@ -106,21 +106,22 @@ use Test::Script 1.27 qw(
EOSTM
chomp $text;
my $replacement = parse_statement($text);
is $replacement->first_token->{_location}, undef,
'replacement has no location data';
is $replacement->location, undef,
'and it cant generate a default location when asked';
ok defined $replacement->first_token->{_location},
'clone preserves location data from source document';
ok defined $replacement->location,
'and it can report a location';

$include2->replace($replacement);

my $nextsib = $replacement->next_sibling;
is_deeply $nextsib->location, [ 4, 91, 91, 4, $file ],
'next token location is stale';

# now the $Document has a node without location, and all
# now the $Document has a node with stale location, and all
# subsequent elements have a stale cached location.

# a partial reindex should fix all location caches:
# flush + reindex should fix all location caches:
$replacement->_flush_locations;
my $res = eval {
use warnings 'FATAL';
$Document->index_locations;
Expand Down
Loading