Skip to content

parser: do not consume the newline that ends a heredoc terminator line - #115

Merged
m-ildefons merged 1 commit into
hadolint:masterfrom
Eljees:fix/heredoc-terminator-keeps-newline
Aug 11, 2026
Merged

parser: do not consume the newline that ends a heredoc terminator line#115
m-ildefons merged 1 commit into
hadolint:masterfrom
Eljees:fix/heredoc-terminator-keeps-newline

Conversation

@Eljees

@Eljees Eljees commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

closes #99
related-to: hadolint/hadolint#960

What I did

Anything that follows a heredoc immediately — a comment or the next instruction — fails to parse:

RUN <<EOF
echo foo
EOF
# comment
Dockerfile:4:1: unexpected '#'
expecting a new line followed by the next instruction

Both reporters noticed that the error disappears if you add an empty line after the terminator,
or a single space before the #. That is the signature of the defect: the character is fine,
the newline is gone.

How I did it

heredocContent (src/Language/Docker/Parser/Prelude.hs) recognises the terminator as

terEOL = string $ "\n" <> marker <> "\n"

so it consumes the line break that comes after the marker. The top-level loop in
Language/Docker/Parser.hs then asks for a separator:

i <- parseInstruction
eol <|> eof <?> "a new line followed by the next instruction"

and there is nothing left to consume — hence the error, and hence the two workarounds: an empty
line supplies a second newline, and a space before # is caught by the onlySpaces1 branch
of eol.

The patch stops at the marker and only looks ahead for the newline, so the line break stays
available for the caller. delEOL (the empty-heredoc delimiter) has the same shape and the same
defect, and is fixed symmetrically — it is reachable: it is the branch taken by the existing
"empty heredoc" test, which I confirmed with a traceM probe before touching it.

terEOF/delEOF (marker at end of file, no trailing newline) are left alone.

How to verify it

cabal test --test-show-details=always
  • five new tests in test/Language/Docker/ParseRunSpec.hs and
    test/Language/Docker/ParseCopySpec.hs: heredoc followed by a comment, heredoc followed by
    another instruction, and the same two for an empty heredoc;
  • before the change: 302 examples, 5 failures, each with the exact error from the issues;
  • after: 302 examples, 0 failures (the 297 pre-existing tests are untouched), including
    "empty heredoc", "empty heredoc not followed by newline" and "evil heredoc";
  • hlint src/ test/ reports the same 5 hints before and after the patch.

Environment: GHC 9.8.4, cabal test as in .github/workflows/haskell.yml.

The heredoc terminator consumed the line break that follows the marker,
so the top level parser had nothing left to accept as the separator
before the next instruction. A comment or an instruction placed directly
after a heredoc failed to parse, while an empty line or a leading space
made the error go away.

Stop at the marker and only look ahead for the newline. The delimiter of
an empty heredoc has the same shape and is fixed the same way.

closes hadolint#99
related-to: hadolint/hadolint#960

Signed-off-by: Eljees <3.14hell@gmail.com>

@m-ildefons m-ildefons left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks a lot. This is awesome.

@m-ildefons
m-ildefons merged commit 5820584 into hadolint:master Aug 11, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

hadolint halts if the character after a heredoc close is not whitespace, reports an invalid error

2 participants