mirror of
https://github.com/caddyserver/caddy.git
synced 2026-08-03 02:52:23 -04:00
caddyfile: token renderer comments (I1/I3) and continuations (D1)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -16,6 +16,7 @@ package caddyfile
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Format formats the input Caddyfile to a standard, nice-looking appearance.
|
||||
@@ -71,7 +72,20 @@ func formatTokens(tokens []Token) []byte {
|
||||
atLineStart = true
|
||||
}
|
||||
|
||||
// prevWasComment tracks whether the last non-whitespace token emitted was a
|
||||
// comment. A structural "{" must never fold onto a line whose last token is
|
||||
// a comment, since "#" runs to end of line and would comment the brace out.
|
||||
prevWasComment := false
|
||||
// foldedOpenAt, when >= 0, is the index of a structural "{" that was already
|
||||
// emitted early (before a trailing comment) as part of the address/comment/
|
||||
// brace fold; when the loop reaches that index it is skipped.
|
||||
foldedOpenAt := -1
|
||||
|
||||
for i := range tokens {
|
||||
if i == foldedOpenAt {
|
||||
// This "{" was already emitted ahead of a trailing comment.
|
||||
continue
|
||||
}
|
||||
tk := tokens[i]
|
||||
isOpen := isOpenCurlyBrace(tk)
|
||||
// A structural close brace only closes a block when one is open. When
|
||||
@@ -80,6 +94,11 @@ func formatTokens(tokens []Token) []byte {
|
||||
// emitted verbatim at its source position.
|
||||
isClose := isCloseCurlyBrace(tk) && nesting > 0
|
||||
|
||||
// A trailing comment shares its source line with the previous token
|
||||
// (e.g. after "}", after "{", or after a directive) and renders inline
|
||||
// on that line; a standalone comment sits alone on its own line.
|
||||
trailingComment := tk.isComment && wrote && !isNextOnNewLine(tokens[i-1], tk)
|
||||
|
||||
// breaks is the number of newlines to emit before this token:
|
||||
// 0 = stay on the current line, 1 = new line, 2 = one blank line.
|
||||
breaks := 0
|
||||
@@ -98,7 +117,13 @@ func formatTokens(tokens []Token) []byte {
|
||||
case isOpen:
|
||||
// An opening brace attaches to the current line: never break to a
|
||||
// new line before it (it joins the preceding token with a space).
|
||||
breaks = 0
|
||||
// EXCEPTION: a standalone comment line must keep the following "{"
|
||||
// on its own line, otherwise the "#" would comment the brace out.
|
||||
if prevWasComment {
|
||||
breaks = 1
|
||||
} else {
|
||||
breaks = 0
|
||||
}
|
||||
case isClose:
|
||||
// A closing brace always goes on its own line, and dedents.
|
||||
if wrote && !atLineStart {
|
||||
@@ -107,21 +132,37 @@ func formatTokens(tokens []Token) []byte {
|
||||
if nesting > 0 {
|
||||
nesting--
|
||||
}
|
||||
case trailingComment:
|
||||
// A trailing comment stays on the current line.
|
||||
breaks = 0
|
||||
}
|
||||
|
||||
// The content following an opening brace always begins on its own
|
||||
// indented line, regardless of how the source was laid out. This
|
||||
// applies to any token, including a nested opening brace.
|
||||
if wrote && isOpenCurlyBrace(tokens[i-1]) && breaks == 0 {
|
||||
// applies to any token, including a nested opening brace. A trailing
|
||||
// comment after "{" is the exception: it stays inline on the "{" line.
|
||||
if wrote && isOpenCurlyBrace(tokens[i-1]) && breaks == 0 && !trailingComment {
|
||||
breaks = 1
|
||||
}
|
||||
|
||||
// A blank line always follows a top-level closing brace.
|
||||
if prevTopClose && breaks < 2 {
|
||||
// A blank line always follows a top-level closing brace, unless this
|
||||
// token is a comment trailing that brace on the same source line.
|
||||
if prevTopClose && breaks < 2 && !trailingComment {
|
||||
breaks = 2
|
||||
}
|
||||
prevTopClose = false
|
||||
|
||||
// Address/comment/brace fold: when this is a trailing comment whose very
|
||||
// next token is a structural "{" that opens this line's block, emit the
|
||||
// "{" on the head line before the comment, so "addr # c"⏎"{" renders as
|
||||
// "addr { # c". This must happen before any newline is emitted.
|
||||
if trailingComment && i+1 < len(tokens) && isOpenCurlyBrace(tokens[i+1]) && breaks == 0 && !atLineStart {
|
||||
out.WriteByte(' ')
|
||||
out.WriteString(tokens[i+1].Raw())
|
||||
nesting++
|
||||
foldedOpenAt = i + 1
|
||||
}
|
||||
|
||||
// Emit the vertical spacing.
|
||||
for breaks > 0 {
|
||||
newline()
|
||||
@@ -133,21 +174,37 @@ func formatTokens(tokens []Token) []byte {
|
||||
writeIndent()
|
||||
}
|
||||
|
||||
// Emit horizontal separation for same-line tokens: exactly one space
|
||||
// separates two tokens that share a line (including an attaching "{").
|
||||
// A structural close brace at nesting zero (an inline literal, see
|
||||
// above) stays glued to its predecessor when the source had no space.
|
||||
if !atLineStart {
|
||||
// A line continuation renders as "\", a newline, then a hanging indent
|
||||
// of nesting+1 tabs before the token (instead of a single space). The
|
||||
// token's Raw() carries the source continuation framing ("\"+newline+
|
||||
// indentation) as a prefix, so it is stripped and re-emitted normalized.
|
||||
body := tk.Raw()
|
||||
if tk.continuation && !atLineStart {
|
||||
out.WriteString(" \\\n")
|
||||
for j := 0; j < nesting+1; j++ {
|
||||
out.WriteByte('\t')
|
||||
}
|
||||
body = strings.TrimLeft(strings.TrimPrefix(body, "\\"), " \t\r\n")
|
||||
atLineStart = false
|
||||
} else if !atLineStart {
|
||||
// Emit horizontal separation for same-line tokens: exactly one space
|
||||
// separates two tokens that share a line (including an attaching "{").
|
||||
// A structural close brace at nesting zero (an inline literal) stays
|
||||
// glued to its predecessor when the source had no space. A comment
|
||||
// glues to a preceding quoted/backtick/heredoc token only when no
|
||||
// space separated them ("x"#c); otherwise it takes a leading space.
|
||||
inlineClose := isCloseCurlyBrace(tk) && !isClose
|
||||
if !(inlineClose && !tk.precededBySpace) {
|
||||
gluedComment := tk.isComment && tokens[i-1].Quoted() && !tk.precededBySpace
|
||||
if !(inlineClose && !tk.precededBySpace) && !gluedComment {
|
||||
out.WriteByte(' ')
|
||||
}
|
||||
}
|
||||
|
||||
// Emit the token body verbatim.
|
||||
out.WriteString(tk.Raw())
|
||||
out.WriteString(body)
|
||||
wrote = true
|
||||
atLineStart = false
|
||||
prevWasComment = tk.isComment
|
||||
|
||||
// Structural bookkeeping after emitting. Vertical spacing after a
|
||||
// brace is produced by the next token's break calculation, so we don't
|
||||
|
||||
@@ -216,13 +216,11 @@ d {
|
||||
# g
|
||||
}
|
||||
|
||||
h {
|
||||
# i
|
||||
h { # i
|
||||
}`,
|
||||
},
|
||||
{
|
||||
description: "quotes and escaping",
|
||||
skip: "task 9: comment placement (I1) keeps a glued trailing comment attached to its token",
|
||||
input: `"a \"b\" "#c
|
||||
d
|
||||
|
||||
@@ -326,7 +324,6 @@ baz`,
|
||||
},
|
||||
{
|
||||
description: "brace does not fold into comment above",
|
||||
skip: "task 9: comment placement (I1) keeps a following { off the comment line",
|
||||
input: `# comment
|
||||
{
|
||||
foo
|
||||
@@ -526,3 +523,32 @@ import ./conf.d/matcher_not_my_subnet.caddy
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFormatCommentsOnBraceLines(t *testing.T) {
|
||||
cases := []struct{ in, want string }{
|
||||
{"site {\n\tfoo\n} # after close\n", "site {\n\tfoo\n} # after close\n"},
|
||||
{"site { # note\n\tfoo\n}\n", "site { # note\n\tfoo\n}\n"},
|
||||
{"site # note\n{\n\tfoo\n}\n", "site { # note\n\tfoo\n}\n"},
|
||||
}
|
||||
for _, c := range cases {
|
||||
if got := string(Format([]byte(c.in))); got != c.want {
|
||||
t.Errorf("in %q:\n got %q\nwant %q", c.in, got, c.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFormatBlankLineCapAfterComment(t *testing.T) {
|
||||
got := string(Format([]byte("foo # inline\n\n\nbar\n")))
|
||||
want := "foo # inline\n\nbar\n"
|
||||
if got != want {
|
||||
t.Errorf("got %q, want %q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFormatContinuationHangingIndent(t *testing.T) {
|
||||
in := "route {\n\treverse_proxy \\\n\ta \\\n\tb\n}\n"
|
||||
want := "route {\n\treverse_proxy \\\n\t\ta \\\n\t\tb\n}\n"
|
||||
if got := string(Format([]byte(in))); got != want {
|
||||
t.Errorf("got %q, want %q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user