diff --git a/lib/PPI/Token/HereDoc.pm b/lib/PPI/Token/HereDoc.pm index d954ff78..42222372 100644 --- a/lib/PPI/Token/HereDoc.pm +++ b/lib/PPI/Token/HereDoc.pm @@ -171,29 +171,34 @@ sub __TOKENIZER__on_char { if ( $content =~ /^\<\<(\w+)$/ ) { # Bareword $token->{_mode} = 'interpolate'; + $token->{_raw_terminator} = $1; $token->{_terminator} = $1; } elsif ( $content =~ /^\<\<\s*\'(.*)\'$/ ) { # ''-quoted literal $token->{_mode} = 'literal'; + $token->{_raw_terminator} = "'$1'"; $token->{_terminator} = $1; $token->{_terminator} =~ s/\\'/'/g; } elsif ( $content =~ /^\<\<\s*\"(.*)\"$/ ) { # ""-quoted literal $token->{_mode} = 'interpolate'; + $token->{_raw_terminator} = "\"$1\""; $token->{_terminator} = $1; $token->{_terminator} =~ s/\\"/"/g; } elsif ( $content =~ /^\<\<\s*\`(.*)\`$/ ) { # ``-quoted command $token->{_mode} = 'command'; + $token->{_raw_terminator} = "`$1`"; $token->{_terminator} = $1; $token->{_terminator} =~ s/\\`/`/g; } elsif ( $content =~ /^\<\<\\(\w+)$/ ) { # Legacy forward-slashed bareword $token->{_mode} = 'literal'; + $token->{_raw_terminator} = "\\$1"; $token->{_terminator} = $1; } else { @@ -207,9 +212,9 @@ sub __TOKENIZER__on_char { # Suck in the HEREDOC $token->{_heredoc} = []; - my $terminator = $token->{_terminator} . "\n"; + my $raw_terminator = $token->{_raw_terminator} . "\n"; while ( defined($line = $t->_get_line) ) { - if ( $line eq $terminator ) { + if ( $line eq $raw_terminator ) { # Keep the actual termination line for consistency # when we are re-assembling the file $token->{_terminator_line} = $line; diff --git a/t/ppi_token_heredoc.t b/t/ppi_token_heredoc.t new file mode 100644 index 00000000..9b7d4385 --- /dev/null +++ b/t/ppi_token_heredoc.t @@ -0,0 +1,67 @@ +#!/usr/bin/perl + +# Unit testing for PPI::Token::HereDoc + +use strict; +BEGIN { + $| = 1; + $^W = 1; + no warnings 'once'; + $PPI::XS_DISABLE = 1; + $PPI::Lexer::X_TOKENIZER ||= $ENV{X_TOKENIZER}; +} +use Test::More tests => 6; +use Test::NoWarnings; +use PPI; + + +HEREDOC: { + my $tests = [ + { + name => 'Bareword terminator.', + content => "my \$heredoc = < 'Single-quoted bareword terminator.', + content => "my \$heredoc = <<'HERE';\nLine 1\nLine 2\n'HERE'\n", + }, + { + name => 'Double-quoted bareword terminator.', + content => "my \$heredoc = <<\"HERE\";\nLine 1\nLine 2\n\"HERE\"\n", + }, + { + name => 'Command-quoted terminator.', + content => "my \$heredoc = <<`HERE`;\nLine 1\nLine 2\n`HERE`\n", + }, + { + name => 'Legacy escaped bareword terminator.', + content => "my \$heredoc = <<\\HERE;\nLine 1\nLine 2\n\\HERE\n", + }, + ]; + + foreach my $test ( @$tests ) { + subtest( + $test->{'name'}, + sub { + plan( tests => 6 ); + + my $document = PPI::Document->new( \$test->{'content'} ); + isa_ok( $document, 'PPI::Document' ); + + my $heredocs = $document->find('Token::HereDoc'); + is( ref($heredocs), 'ARRAY', 'Found heredocs.' ); + is( scalar(@$heredocs), 1, 'Found 1 heredoc block.' ); + my $heredoc = $heredocs->[0]; + isa_ok( $heredoc, 'PPI::Token::HereDoc'); + can_ok( $heredoc, 'heredoc' ); + + my @content = $heredoc->heredoc(); + unlike( + $content[-1], + qr/HERE/, + 'The returned content does not include the heredoc terminator.', + ) || diag( "heredoc() returned ", explain( \@content ) ); + } + ); + } +}