caddyfile: keep an opening brace that ends the input (#8047)

* caddyfile: Keep an opening brace that ends the input

Format writes an opening brace lazily: the '{' case records the pending
brace and continues, and the brace is only emitted once a following
regular character is processed. When the input ends while a brace is
still pending, the read loop exits and nothing flushes it, so the brace
is dropped. 'caddy fmt' on a Caddyfile whose last line has just opened a
block therefore deletes the brace the user typed, and with --overwrite
that result is written back over the file.

Flush a pending brace after the loop ends. This also restores
idempotence for inputs such as '{ {', which formatted to '{' and then to
nothing.

* caddyfile: Flush a pending brace before another one replaces it

The '{' case overwrote the pending-brace state without emitting the
brace already waiting, so adjacent braces lost one: Format("{{") gave
just "{". Only the space-separated path flushed, and it did so with an
inline block.

Move that block into a flushOpenBrace closure and call it from the three
places a pending brace has to be emitted: the existing space-separated
path, the '{' case before it replaces the state, and the end of input.
Using it at the end of input also indents a trailing brace the same way
the other paths do.
This commit is contained in:
Roland authored and GitHub committed 2026-10-03 11:16:42 +10:00
1 parent 2f34b936f5
commit b72bf7a1ef
2 files changed
+58 -25

No files matched your search

+36 -25
View File
@@ -106,6 +106,34 @@ func Format(input []byte) []byte {
beginningOfLine = true
}
flushOpenBrace := func() {
if nesting == 0 && last == '}' {
nextLine()
nextLine()
}
openBrace = false
if openBraceOwnLine && previousLineWasTopLevelImport {
if last != '\n' {
nextLine()
}
indent()
} else if beginningOfLine {
indent()
} else if !openBraceSpace || !unicode.IsSpace(last) {
write(' ')
}
write('{')
openBraceWritten = true
openBraceOwnLine = false
nextLine()
newLines = 0
// prevent infinite nesting from ridiculous inputs (issue #4169)
if nesting < 10 {
nesting++
}
}
for {
ch, _, err := rdr.ReadRune()
if err != nil {
@@ -270,36 +298,15 @@ func Format(input []byte) []byte {
}
if openBrace && spacePrior && !openBraceWritten {
if nesting == 0 && last == '}' {
nextLine()
nextLine()
}
openBrace = false
if openBraceOwnLine && previousLineWasTopLevelImport {
if last != '\n' {
nextLine()
}
indent()
} else if beginningOfLine {
indent()
} else if !openBraceSpace || !unicode.IsSpace(last) {
write(' ')
}
write('{')
openBraceWritten = true
openBraceOwnLine = false
nextLine()
newLines = 0
// prevent infinite nesting from ridiculous inputs (issue #4169)
if nesting < 10 {
nesting++
}
flushOpenBrace()
}
switch {
case ch == '{':
finishToken()
if openBrace && !openBraceWritten {
flushOpenBrace()
}
openBrace = true
openBraceSpace = spacePrior && !beginningOfLine
openBraceOwnLine = newLines > 0
@@ -367,6 +374,10 @@ func Format(input []byte) []byte {
beginningOfLine = false
}
if openBrace && !openBraceWritten {
flushOpenBrace()
}
// the Caddyfile does not need any leading or trailing spaces, but...
trimmedResult := bytes.TrimSpace(out.Bytes())
+22
View File
@@ -495,6 +495,28 @@ import ./conf.d/matcher_not_my_subnet.caddy
order appsec after crowdsec
}`,
},
{
description: "keep an opening brace that ends the input",
input: `localhost {`,
expect: `localhost {`,
},
{
description: "keep a standalone opening brace that ends the input",
input: `{`,
expect: `{`,
},
{
description: "keep adjacent opening braces that end the input",
input: `{{`,
expect: `{
{`,
},
{
description: "keep spaced opening braces that end the input",
input: `{ {`,
expect: `{
{`,
},
} {
// the formatter should output a trailing newline,
// even if the tests aren't written to expect that