feat(format): expand the runtime safety gate
Replace the bare SemanticallyEqual call in every frontend (CLI fmt, LSP
formatting + rangeFormatting) with format.VerifySafe, which runs four
checks before any formatted output is emitted:
- semantic equivalence - the code token stream is unchanged
- comment preservation - no -- or /* */ comment is dropped, merged,
split, reordered, or reworded (line endings / indentation normalised
away; recurses into dollar-quoted bodies)
- structural balance - the () [] and BEGIN/CASE/IF/LOOP...END nesting
profile matches, ignoring anything inside a comment or a string
- idempotence - a second format pass would not change it
On failure the CLI now prints the specific reason and keeps the original.
The comment check surfaced two real formatter bugs, both fixed in
formatBodyStatements:
- multi-line /* */ comments inside a PL/pgSQL body had their interior
lines re-split and reindented as if they were statements; they are
now tracked and carried verbatim with the opening line
- a column-0 -- line was glued onto the preceding line by the
split-line-join, which merged consecutive comment lines into one
Regenerate testdata/corpus/test_mm_proc.pgsql (was carrying the mangled
output). TestCorpusIdempotentAndSafe now runs the full VerifySafe bundle;
add safety_test.go with targeted cases.
This commit is contained in:
+36
-1
@@ -398,6 +398,22 @@ func formatBodyStatements(text string, st config.Style) string {
|
||||
normalised := strings.ReplaceAll(text, "\r\n", "\n")
|
||||
rawLines := strings.Split(normalised, "\n")
|
||||
|
||||
// Mark the continuation lines of every multi-line /* … */ block comment.
|
||||
// Those lines are comment content, not code: they must be carried verbatim
|
||||
// with the comment's opening line, never split off and reindented as if
|
||||
// they were statements of their own.
|
||||
inBlockComment := make([]bool, len(rawLines))
|
||||
for _, t := range lexer.Lex(normalised) {
|
||||
if t.Kind != lexer.BlockComment {
|
||||
continue
|
||||
}
|
||||
n := strings.Count(t.Text, "\n")
|
||||
start := t.Line - 1 // lexer Line is 1-based within normalised
|
||||
for k := 1; k <= n && start+k < len(inBlockComment); k++ {
|
||||
inBlockComment[start+k] = true
|
||||
}
|
||||
}
|
||||
|
||||
maxBlanks := st.PlpgsqlMaxBlankLines
|
||||
if maxBlanks < 0 {
|
||||
maxBlanks = 0
|
||||
@@ -488,8 +504,21 @@ func formatBodyStatements(text string, st config.Style) string {
|
||||
stmt = nil
|
||||
}
|
||||
|
||||
for _, rawLine := range rawLines {
|
||||
for j, rawLine := range rawLines {
|
||||
line := strings.TrimRight(rawLine, "\r")
|
||||
|
||||
if inBlockComment[j] {
|
||||
// Verbatim continuation of a multi-line block comment: glue it to the
|
||||
// bline holding the comment's opening line.
|
||||
if len(stmt) > 0 {
|
||||
last := &stmt[len(stmt)-1]
|
||||
last.text += "\n" + line
|
||||
} else {
|
||||
stmt = append(stmt, bline{text: line})
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
indent := leadingWhitespace(line)
|
||||
stripped := line[len(indent):]
|
||||
|
||||
@@ -502,6 +531,12 @@ func formatBodyStatements(text string, st config.Style) string {
|
||||
fw := lowerASCII(firstBodyKeyword(stripped))
|
||||
isColZero := indent == ""
|
||||
joinToPrev := isColZero && parenDepth == 0 && len(stmt) > 0 && !sqlClauseKw[fw]
|
||||
// A col-0 comment-only line is its own thing: never glue it onto the
|
||||
// previous line — doing so buries a code line's trailing text in a
|
||||
// comment and collapses consecutive -- comment lines into one.
|
||||
if joinToPrev && len(significantBodyTokens(stripped)) == 0 {
|
||||
joinToPrev = false
|
||||
}
|
||||
// Don't join a col-0 continuation to a comment-only preceding bline:
|
||||
// the comment has no structural keyword so `continue ;` at col-0 would
|
||||
// disappear into the comment text and be invisible to the lexer.
|
||||
|
||||
Reference in New Issue
Block a user