Skip to content

Commit e3d9048

Browse files
guillaumeaubertwchristian
authored andcommitted
Fix parsing heredocs without a trailing newline.
1 parent f81ae48 commit e3d9048

File tree

2 files changed

+81
-20
lines changed

2 files changed

+81
-20
lines changed

lib/PPI/Token/HereDoc.pm

Lines changed: 17 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -201,14 +201,10 @@ sub __TOKENIZER__on_char {
201201
return undef;
202202
}
203203

204-
# Define $line outside of the loop, so that if we encounter the
205-
# end of the file, we have access to the last line still.
206-
my $line;
207-
208204
# Suck in the HEREDOC
209205
$token->{_heredoc} = [];
210206
my $terminator = $token->{_terminator} . "\n";
211-
while ( defined($line = $t->_get_line) ) {
207+
while ( defined( my $line = $t->_get_line ) ) {
212208
if ( $line eq $terminator ) {
213209
# Keep the actual termination line for consistency
214210
# when we are re-assembling the file
@@ -224,24 +220,25 @@ sub __TOKENIZER__on_char {
224220

225221
# End of file.
226222
# Error: Didn't reach end of here-doc before end of file.
227-
# $line might be undef if we get NO lines.
228-
if ( defined $line and $line eq $token->{_terminator} ) {
229-
# If the last line matches the terminator
230-
# but is missing the newline, we want to allow
231-
# it anyway (like perl itself does). In this case
232-
# perl would normally throw a warning, but we will
233-
# also ignore that as well.
234-
pop @{$token->{_heredoc}};
235-
$token->{_terminator_line} = $line;
236-
} else {
237-
# The HereDoc was not properly terminated.
238-
$token->{_terminator_line} = undef;
239223

240-
# Trim off the trailing whitespace
241-
if ( defined $token->{_heredoc}->[-1] and $t->{source_eof_chop} ) {
242-
chop $token->{_heredoc}->[-1];
224+
# If the here-doc block is not empty, look at the last line to determine if
225+
# the here-doc terminator is missing a newline (which Perl would fail to
226+
# compile but is easy to detect) or if the here-doc block was just not
227+
# terminated at all (which Perl would fail to compile as well).
228+
$token->{_terminator_line} = undef;
229+
if ( @{$token->{_heredoc}} and defined $token->{_heredoc}[-1] ) {
230+
# See PPI::Tokenizer, the algorithm there adds a space at the end of the
231+
# document that we need to make sure we remove.
232+
if ( $t->{source_eof_chop} ) {
233+
chop $token->{_heredoc}[-1];
243234
$t->{source_eof_chop} = '';
244235
}
236+
237+
# Check if the last line of the file matches the terminator without
238+
# newline at the end. If so, remove it from the content and set it as
239+
# the terminator line.
240+
$token->{_terminator_line} = pop @{$token->{_heredoc}}
241+
if $token->{_heredoc}[-1] eq $token->{_terminator};
245242
}
246243

247244
# Set a hint for PPI::Document->serialize so it can

t/ppi_token_heredoc.t

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,70 @@ my @tests = (
7676
},
7777
},
7878

79+
# Tests without a carriage return after the termination marker.
80+
{
81+
name => 'Bareword terminator (no return).',
82+
content => "my \$heredoc = <<HERE;\nLine 1\nLine 2\nHERE",
83+
expected => {
84+
_terminator_line => 'HERE',
85+
_damaged => 1,
86+
_terminator => 'HERE',
87+
_mode => 'interpolate',
88+
},
89+
},
90+
{
91+
name => 'Single-quoted bareword terminator (no return).',
92+
content => "my \$heredoc = <<'HERE';\nLine 1\nLine 2\nHERE",
93+
expected => {
94+
_terminator_line => "HERE",
95+
_damaged => 1,
96+
_terminator => 'HERE',
97+
_mode => 'literal',
98+
},
99+
},
100+
{
101+
name => 'Double-quoted bareword terminator (no return).',
102+
content => "my \$heredoc = <<\"HERE\";\nLine 1\nLine 2\nHERE",
103+
expected => {
104+
_terminator_line => 'HERE',
105+
_damaged => 1,
106+
_terminator => 'HERE',
107+
_mode => 'interpolate',
108+
},
109+
},
110+
{
111+
name => 'Command-quoted terminator (no return).',
112+
content => "my \$heredoc = <<`HERE`;\nLine 1\nLine 2\nHERE",
113+
expected => {
114+
_terminator_line => 'HERE',
115+
_damaged => 1,
116+
_terminator => 'HERE',
117+
_mode => 'command',
118+
},
119+
},
120+
{
121+
name => 'Legacy escaped bareword terminator (no return).',
122+
content => "my \$heredoc = <<\\HERE;\nLine 1\nLine 2\nHERE",
123+
expected => {
124+
_terminator_line => 'HERE',
125+
_damaged => 1,
126+
_terminator => 'HERE',
127+
_mode => 'literal',
128+
},
129+
},
130+
131+
# Tests without a terminator.
132+
{
133+
name => 'Unterminated heredoc block.',
134+
content => "my \$heredoc = <<HERE;\nLine 1\nLine 2\n",
135+
expected => {
136+
_terminator_line => undef,
137+
_damaged => 1,
138+
_terminator => 'HERE',
139+
_mode => 'interpolate',
140+
},
141+
}
142+
79143
);
80144

81145
plan tests => 1 + @tests;

0 commit comments

Comments
 (0)