feat(format): skip non-plpgsql bodies; keep literals and embedded code safe

- Only restyle bodies for plpgsql/sql; skip pl* (plpython3u, plperl, ...), c and internal
- Keep LANGUAGE clause on DO blocks
- Carry multi-line string and dollar-quoted literals verbatim in bodies and DML
- Format dollar-quoted literals that contain code (declare/begin/select/...), never format() templates
- Fix code joined onto -- comments, early flush after raise exception, dropped comment before BEGIN
- Regenerate test_mm_proc golden; document in README, plan, todo
This commit is contained in:
Hein
2026-10-06 14:43:43 +02:00
parent 41fdaf415c
commit 4ff729eeeb
8 changed files with 729 additions and 228 deletions
+62 -5
View File
@@ -55,7 +55,7 @@ func (p *printer) writeItem(n cst.Node) {
case *cst.Raw:
switch {
case isDMLStart(v.Toks):
p.b.WriteString(formatDML(v.Toks, p.st))
p.b.WriteString(restoreLiteralNewlines(formatDML(v.Toks, p.st)))
case isDoBlock(v.Toks):
p.b.WriteString(formatDoBlock(v.Toks, p.st))
default:
@@ -82,8 +82,10 @@ func isDoBlock(toks []cst.Tok) bool {
// formatDoBlock formats a DO $$ ... $$ block by applying formatBody to the
// dollar-quoted string and emitting DO + newline + formatted body.
func formatDoBlock(toks []cst.Tok, st config.Style) string {
// Find the DO keyword, the dollar-string body, and the optional semicolon.
// Find the DO keyword, the dollar-string body, any other clause tokens
// (LANGUAGE x, before or after the body), and the optional semicolon.
var doTok, bodyTok *cst.Tok
var pre, post []string
hasSemi := false
for i := range toks {
t := &toks[i]
@@ -101,6 +103,21 @@ func formatDoBlock(toks []cst.Tok, st config.Style) string {
}
if t.Tok.Kind == lexer.Semicolon {
hasSemi = true
continue
}
// Clause tokens other than DO / body / ';' (e.g. LANGUAGE plpython3u).
// Any comment attached to one means we cannot safely relocate it.
if len(t.Comments()) > 0 {
return verbatimSpanFormatBody(toks, bodyTok, st)
}
txt := t.Tok.Text
if low == "language" {
txt = applyCase(txt, st.KeywordCase)
}
if bodyTok == nil {
pre = append(pre, txt)
} else {
post = append(post, txt)
}
}
if doTok == nil || bodyTok == nil {
@@ -110,8 +127,20 @@ func formatDoBlock(toks []cst.Tok, st config.Style) string {
nl := st.Newline
var b strings.Builder
b.WriteString(applyCase(doTok.Tok.Text, st.KeywordCase))
if len(pre) > 0 {
b.WriteString(" ")
b.WriteString(strings.Join(pre, " "))
}
b.WriteString(nl)
b.WriteString(formatBody(bodyTok.Tok.Text, st))
if isPlpgsql(toks) {
b.WriteString(formatBody(bodyTok.Tok.Text, st))
} else {
b.WriteString(bodyTok.Tok.Text)
}
if len(post) > 0 {
b.WriteString(nl)
b.WriteString(strings.Join(post, " "))
}
if hasSemi {
b.WriteString(";")
}
@@ -193,7 +222,11 @@ func (p *printer) writeCreateFunction(cf *cst.CreateFunction) {
}
if cf.Body != nil {
p.nl()
p.b.WriteString(formatBody(cf.Body.Tok.Text, p.st))
if isPlpgsql(cst.Tokens(cf)) {
p.b.WriteString(formatBody(cf.Body.Tok.Text, p.st))
} else {
p.b.WriteString(cf.Body.Tok.Text)
}
}
for _, clause := range cf.Tail {
p.nl()
@@ -445,7 +478,7 @@ func verbatimSpanFormatBody(toks []cst.Tok, bodyTok *cst.Tok, st config.Style) s
b.WriteString(tr.Text)
}
}
if bodyTok != nil && t.Tok.Kind == lexer.DollarString && t.Tok.Off == bodyTok.Tok.Off {
if bodyTok != nil && t.Tok.Kind == lexer.DollarString && t.Tok.Off == bodyTok.Tok.Off && isPlpgsql(toks) {
b.WriteString(formatBody(t.Tok.Text, st))
} else {
b.WriteString(t.Tok.Text)
@@ -546,3 +579,27 @@ func alignParamTypes(params []string) []string {
}
return out
}
// isPlpgsql reports whether the routine's body should be restyled. Bodies in
// any pl* language other than plpgsql (plpython3u, plperl, pltcl, ...), and in
// c or internal, are emitted verbatim; plpgsql, sql and everything else are
// formatted. A routine
// with no LANGUAGE clause is treated as sql and formatted.
func isPlpgsql(toks []cst.Tok) bool {
var sig []cst.Tok
for _, t := range toks {
if !t.Tok.IsTrivia() && t.Tok.Kind != lexer.EOF {
sig = append(sig, t)
}
}
for i := 0; i+1 < len(sig); i++ {
if sig[i].Is("language") {
name := strings.ToLower(strings.Trim(sig[i+1].Tok.Text, "'\""))
if name == "c" || name == "internal" {
return false
}
return !strings.HasPrefix(name, "pl") || name == "plpgsql"
}
}
return true
}