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:
+62
-5
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user