Skip to content

Preserve empty continuation lines (backslash-only separator lines) in RUN #59

Closed

Description

@d-enk

Problem

dockerfmt removes empty continuation lines — lines containing only a backslash (\) — that are used as visual separators between logical groups inside long RUN instructions:

FROM alpine
RUN \
  --mount=type=bind,source=./a,target=/b \
  \
  echo one \
  \
  && echo two \
  \
  && echo three

is formatted to:

FROM alpine
RUN --mount=type=bind,source=./a,target=/b \
    echo one \
    && echo two \
    && echo three

All three separator lines are gone.

These lines are valid Dockerfile syntax (a continuation line with empty content). No linter complains about them: BuildKit's NoEmptyContinuation build check only fires on completely blank lines inside a continuation, and hadolint has no rule for them. So removing them is a pure formatting-style loss, and users currently have no way to keep them short of # dockerfmt-ignore on the whole directive.

Root cause

formatRun hands the RUN body to mvdan/sh (formatBash → syntax.Printer), and the mvdan/sh printer only preserves empty continuation lines in some positions. Probes with plain shfmt -bn:

Position of the \-only line Result
between two plain words preserved
before a line starting with && dropped
at the start of the content dropped

The third case is what bites after extractDirectiveContent splits the --mount flags off the body: any separator that followed the flags becomes a leading empty continuation and is dropped. Separators before && lines are dropped by the printer as well.

Suggested fix

dockerfmt already has a placeholder mechanism that lets comments survive the shfmt pass (preprocessShellComments / postprocessShellComments wrap comment lines in backtick placeholders and unwrap them afterwards). The same trick could be applied to \-only lines: temporarily replace each empty continuation line with a placeholder comment line (which the printer preserves in all positions), then unwrap it back into a bare \ after printing. That would preserve the separators everywhere, including before && lines and right after the flags.

Related: comments in the flags region are dropped

A manual workaround for the above is to use comment lines as separators. In the command region this works well and round-trips:

RUN \
  --mount=type=bind,source=./a,target=/b \
  # --- section
  echo one \
  # --- next
  && echo two

However, comments placed before or between RUN flags are silently removed, together with the leading RUN \ continuation:

FROM alpine
RUN \
  # \
  --mount=type=bind,source=./a,target=/b \
  --mount=type=cache,target=/c \
  echo one

→ (single pass):

FROM alpine
RUN --mount=type=bind,source=./a,target=/b \
    --mount=type=cache,target=/c \
    echo one

The same happens with a comment placed between two --mount flags. Since comments in the command region are preserved (and re-indented — a great feature), it would be consistent to preserve them in the flags region too.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions